]> git.mar77i.info Git - bigintmandel/blob - bigintmandelwidget.cpp
328fbdbc0a901053e8d0ee264f9141b085da1df0
[bigintmandel] / bigintmandelwidget.cpp
1
2 // bigintmandelwidget.cpp
3
4 #include <QFileDialog>
5 #include <QLayout>
6 #include <QMouseEvent>
7 #include <QPainter>
8 #include <QPushButton>
9 #include <QtConcurrent/QtConcurrent>
10
11 #include "bigintmandelwidget.h"
12
13 static inline QMenuBar *setup_menu_bar(BigintMandelWidget *parent) {
14 QMenuBar *menu_bar = new QMenuBar(parent);
15 QMenu *menu = new QMenu("&File", parent);
16 QPushButton *button = new QPushButton("Settings");
17 QAction *two, *four, *eight, *sixteen;
18 menu_bar->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
19 QObject::connect(
20 menu->addAction("&Reset"),
21 &QAction::triggered,
22 parent,
23 &BigintMandelWidget::reset
24 );
25 QObject::connect(
26 menu->addAction("&Load"),
27 &QAction::triggered,
28 parent,
29 &BigintMandelWidget::load_data
30 );
31 QObject::connect(
32 menu->addAction("&Save"),
33 &QAction::triggered,
34 parent,
35 &BigintMandelWidget::save_data
36 );
37 menu->addSeparator();
38 QObject::connect(
39 menu->addAction("&Export"),
40 &QAction::triggered,
41 parent,
42 &BigintMandelWidget::export_img
43 );
44 menu->addSeparator();
45 QObject::connect(
46 menu->addAction("E&xit"),
47 &QAction::triggered,
48 parent,
49 &BigintMandelWidget::close
50 );
51 menu_bar->addMenu(menu);
52 menu = new QMenu("&Zoom factor", parent);
53 two = menu->addAction("&2");
54 four = menu->addAction("&4");
55 eight = menu->addAction("&8");
56 sixteen = menu->addAction("1&6");
57 two->setCheckable(true);
58 two->setChecked(true);
59 four->setCheckable(true);
60 eight->setCheckable(true);
61 sixteen->setCheckable(true);
62 QObject::connect(
63 two,
64 &QAction::triggered,
65 parent,
66 [two, four, eight, sixteen, parent]() {
67 two->setChecked(true);
68 four->setChecked(false);
69 eight->setChecked(false);
70 sixteen->setChecked(false);
71 parent->set_zoom_factor(2);
72 }
73 );
74 QObject::connect(
75 four,
76 &QAction::triggered,
77 parent,
78 [two, four, eight, sixteen, parent]() {
79 two->setChecked(false);
80 four->setChecked(true);
81 eight->setChecked(false);
82 sixteen->setChecked(false);
83 parent->set_zoom_factor(4);
84 }
85 );
86 QObject::connect(
87 eight,
88 &QAction::triggered,
89 parent,
90 [two, four, eight, sixteen, parent]() {
91 two->setChecked(false);
92 four->setChecked(false);
93 eight->setChecked(true);
94 sixteen->setChecked(false);
95 parent->set_zoom_factor(8);
96 }
97 );
98 QObject::connect(
99 sixteen,
100 &QAction::triggered,
101 parent,
102 [two, four, eight, sixteen, parent]() {
103 two->setChecked(false);
104 four->setChecked(false);
105 eight->setChecked(false);
106 sixteen->setChecked(true);
107 parent->set_zoom_factor(16);
108 }
109 );
110 menu_bar->addMenu(menu);
111 QObject::connect(
112 button,
113 &QPushButton::clicked,
114 parent,
115 &BigintMandelWidget::exec_settings_widget
116 );
117 menu_bar->setCornerWidget(button);
118 return menu_bar;
119 }
120
121 static inline void start_calculation(
122 QFutureWatcher<MandelResultCell> *fw, QVector<MandelCell> cells
123 ) {
124 fw->setFuture(
125 QtConcurrent::mapped(
126 cells,
127 [](const MandelCell &cell){ return cell.iterate(); }
128 )
129 );
130 }
131
132 BigintMandelWidget::BigintMandelWidget(QWidget *parent)
133 : QWidget(parent),
134 fw(new QFutureWatcher<MandelResultCell>(this)),
135 settings(128, QSize(502, 334)),
136 scroll_area(new QScrollArea(this)),
137 img_label(new QLabel(this)),
138 img_dirty(true),
139 status_bar(new QStatusBar(this)),
140 settings_widget(new SettingsWidget(this)),
141 draw_progress(-1)
142 {
143 scroll_area->setWidget(img_label);
144 connect(
145 settings_widget,
146 &QDialog::accepted,
147 this,
148 &BigintMandelWidget::settings_widget_accepted
149 );
150 connect(
151 fw,
152 &QFutureWatcher<MandelResultCell>::resultReadyAt,
153 this,
154 &BigintMandelWidget::finished_cell
155 );
156 connect(
157 fw,
158 &QFutureWatcher<MandelResultCell>::finished,
159 this,
160 &BigintMandelWidget::finished
161 );
162 start_calculation(fw, settings.get_cells());
163 setLayout(new QVBoxLayout());
164 layout()->addWidget(setup_menu_bar(this));
165 layout()->addWidget(scroll_area);
166 status_bar->setSizeGripEnabled(false);
167 layout()->addWidget(status_bar);
168 img_label->resize(settings.get_params().get_size());
169 }
170
171 BigintMandelWidget::~BigintMandelWidget() {
172 fw->cancel();
173 fw->waitForFinished();
174 }
175
176 void BigintMandelWidget::finished_cell(int num) {
177 int y = num / settings.get_params().get_size().width();
178 if (y > draw_progress)
179 draw_progress = y;
180 settings.finished_cell(num, fw->resultAt(num));
181 update_img();
182 }
183
184 void BigintMandelWidget::finished() {
185 draw_progress = -1;
186 settings_widget->set_finished(true);
187 update_img();
188 }
189
190 static inline QPixmap enhance_pixmap(QPixmap pixmap, int draw_progress) {
191 QPainter qp;
192 if (draw_progress > -1) {
193 qp.begin(&pixmap);
194 qp.setPen(Qt::GlobalColor::gray);
195 qp.drawLine(0, draw_progress, pixmap.width() - 1, draw_progress);
196 qp.end();
197 }
198 return pixmap;
199 }
200
201 static inline void calculating_status(
202 QStatusBar *status_bar, int &prev_num_threads
203 ) {
204 QTextStream ss(new QString());
205 int num_threads = QThreadPool::globalInstance()->activeThreadCount();
206 if (prev_num_threads == num_threads)
207 return;
208 ss << "Calculating with " << num_threads << " threads ...";
209 status_bar->showMessage(ss.readAll());
210 prev_num_threads = num_threads;
211 }
212
213 static inline void finished_status(QStatusBar *status_bar) {
214 status_bar->showMessage("Click the rendering to zoom.");
215 }
216
217 void BigintMandelWidget::paintEvent(QPaintEvent *event) {
218 static int prev_num_threads = -1;
219 if (!img_dirty)
220 return;
221 img_label->setPixmap(enhance_pixmap(settings.get_pixmap(), draw_progress));
222 img_label->resize(img_label->pixmap().size());
223 img_dirty = false;
224 if (fw->isFinished())
225 finished_status(status_bar);
226 else {
227 calculating_status(status_bar, prev_num_threads);
228 prev_num_threads = -1;
229 }
230 }
231
232 void BigintMandelWidget::mousePressEvent(QMouseEvent *event) {
233 QSize size(settings.get_params().get_size());
234 QPoint pos(event->pos());
235 QWidget *w;
236 for (w = img_label; w != this; w = w->parentWidget())
237 pos = w->mapFromParent(pos);
238 if (event->button() != Qt::MouseButton::LeftButton
239 || !fw->isFinished()
240 || pos.x() < 0
241 || pos.x() >= size.width()
242 || pos.y() < 0
243 || pos.y() >= size.height())
244 return;
245 settings.zoom(get_ideal_size(), zoom_factor, pos);
246 start_calculation(fw, settings.get_cells());
247 update_img();
248 }
249
250 void BigintMandelWidget::update_img() {
251 img_dirty = true;
252 update();
253 }
254
255 static inline QString get_save_file_name(
256 QWidget *parent = nullptr,
257 const QString &caption = QString(),
258 const QString &dir = QString(),
259 const QString &filter_title = QString(),
260 const QStringList &ext_list = QStringList{},
261 const QString default_ext = QString()
262 ) {
263 QTextStream ss(new QString());
264 QStringList::const_iterator ext_list_iter;
265 QString file_name;
266 qsizetype pos;
267 ss << filter_title << " (" << ext_list.join(" ") << ")";
268 file_name = QFileDialog::getSaveFileName(
269 parent, caption, dir, *ss.string()
270 );
271 if (file_name.isEmpty())
272 return file_name;
273 ss.seek(0);
274 ss.string()->clear();
275 ss << file_name;
276 pos = file_name.lastIndexOf(".");
277 if (pos < 0 || !ext_list.contains(file_name.mid(pos)))
278 ss << default_ext;
279 return *ss.string();
280 }
281
282 void BigintMandelWidget::export_img() {
283 QString file_name = get_save_file_name(
284 this,
285 "Save image",
286 "",
287 "Images",
288 QStringList{".png", ".xpm", ".jpg"},
289 ".png"
290 );
291 if (!file_name.isEmpty())
292 settings.save_img(file_name);
293 }
294
295 void BigintMandelWidget::exec_settings_widget() {
296 settings_widget->update_fields(settings.get_params(), fw->isFinished());
297 settings_widget->exec();
298 }
299
300 void BigintMandelWidget::reset() {
301 fw->cancel();
302 fw->waitForFinished();
303 settings.reset(128, get_ideal_size());
304 start_calculation(fw, settings.get_cells());
305 update_img();
306 }
307
308 void BigintMandelWidget::settings_widget_accepted() {
309 settings.set_max_iter(settings_widget->get_max_iter().toULongLong());
310 start_calculation(fw, settings.get_cells());
311 update_img();
312 }
313
314 void BigintMandelWidget::load_data() {
315 QFile file;
316 QJsonObject json;
317 QByteArray qba;
318 QString file_name = QFileDialog::getOpenFileName(
319 this, "Load Data", "", "BigintMandel Data (*.json *.json.qcompress)"
320 );
321 if (file_name.isEmpty())
322 return;
323 file.setFileName(file_name);
324 if (!file.open(QIODevice::ReadOnly)) {
325 qWarning("Couldn't open save file.");
326 return;
327 }
328 qba = file.readAll();
329 file.close();
330 if (file_name.endsWith(".qcompress"))
331 qba = qUncompress(qba);
332 settings.from_json(QJsonDocument::fromJson(qba).object());
333 update_img();
334 }
335
336 void BigintMandelWidget::save_data() {
337 QFile file;
338 QByteArray qba;
339 QString file_name = get_save_file_name(
340 this,
341 "Save data",
342 "",
343 "Bigintmandel Data",
344 QStringList{".json", ".json.qcompress"},
345 ".json.qcompress"
346 );
347 if (file_name.isEmpty())
348 return;
349 qba = QJsonDocument(settings.to_json()).toJson();
350 if (file_name.endsWith(".qcompress"))
351 qba = qCompress(qba, 9);
352 file.setFileName(file_name);
353 if (!file.open(QIODevice::WriteOnly)) {
354 qWarning("Couldn't open save file.");
355 return;
356 }
357 file.write(qba);
358 file.close();
359 }