diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8e27d095..088d13e5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,9 +10,9 @@ jobs: steps: - uses: actions/checkout@v1 - name: prerequisites - run: sudo apt update && sudo apt install build-essential qttools5-dev-tools qt5-default + run: sudo apt update && sudo apt install build-essential qttools5-dev-tools qt6-base-dev libgl1-mesa-dev - name: qmake - run: qmake + run: qmake6 - name: make run: make @@ -23,7 +23,9 @@ jobs: steps: - uses: actions/checkout@v1 - name: install-qt - uses: jurplel/install-qt-action@v2 + uses: jurplel/install-qt-action@v3 + with: + version: '6.2.4' - name: qmake run: qmake - name: make diff --git a/CREDITS.md b/CREDITS.md index 20ab5451..f626e0d7 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -18,7 +18,7 @@ - [Jens Kilian](mailto:jjk@acm.org): BeOS driver, deutsch.cat - [Thomas A. K. Kjaer](mailto:takjaer@imv.aau.dk): OS/2 ports (320x200 graphics and AA-lib) - [Zbyněk Konečný](mailto:zbynek@geogebra.org): Czech translation -- [Zoltán Kovács](mailto:zoltan@geogebra.org): Internationalization, Hungarian translations, finalizing version 3.1, bug fixes, web design, current maintainer +- [Zoltán Kovács](mailto:zoltan@geogebra.org): Internationalization, Hungarian translations, finalizing version 3.1, bug fixes, web design, WebAssembly version, current maintainer - [Zsigmond Kovács](mailto:kovzsi@gmail.com): Fractal examples - [Bjarnheiður Kristinsdóttir](mailto:bjarnhek@hi.is): Icelandic translation - [J.B. Langston III](mailto:jb-langston@austin.rr.com): Native Mac OS X port (from version 3.2.2); web redesign; co-maintainer @@ -39,6 +39,7 @@ - [Ilinca Sitaru](mailto:ilinca.sitaru@gmail.com): Romanian translation - Daniel Skarda: Fractal examples - Andrew Stone ([Stone Design](www.stone.com)): Videator Support, Cocoa improvements, performance mode, bug fixes +- [Abhishek Tiwari](mailto:tabhishek432@gmail.com): WebAssembly version, [contributions for GSoC 2023](https://github.com/tabhishek432/GSoC-2023-xaos) - [Márton Török](mailto:marton.torok@gmail.com): Small fixes for pipes - [Pavel Tzekov](mailto:paveltz@csoft.bg): Win32 support - Charles Vidal: Tcl/Tk interface @@ -54,7 +55,7 @@ XaoS uses the following libraries. These libraries may be included with some binary distributions of XaoS. ### [Qt](https://www.qt.io/) -Copyright © 2020 The Qt Company +Copyright © 2023 The Qt Company License GPLv2+: [GNU GPL version 2 or later](https://gnu.org/licenses/gpl.html) diff --git a/NEWS b/NEWS index a62426c6..951783c3 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,23 @@ +RELEASE NOTES FOR XAOS VERSION 4.3 +---------------------------------- + +New Features +============ + +o Migration to Qt 6. + +o WebAssembly port, that is, a full-featured web version is available. Tested with Qt >= 6.5.2. + +o Progress bar for time consuming operations (only on native platforms). + +Bug Fixes +========= + +o Example files are loaded properly even if XaoS is not installed (that is, the examples are not flattened). + +o User formulas are not duplicated in the history. + + RELEASE NOTES FOR XAOS VERSION 4.2.1 ------------------------------------ diff --git a/README.md b/README.md index f3f4672d..22b50dca 100644 --- a/README.md +++ b/README.md @@ -7,14 +7,16 @@ choose without the long calculation required by other fractal generators. It has many other features too, like different fractal types, autopilot, special coloring modes, random palette generation, color cycling, etc. -You can try the XaoS zooming algorithm online using -[XaoS.js](http://xaos-project.github.io/XaoSjs/). +XaoS (since version 4.3, September 2023) is available as a full-featured web application as well. +You can try it at its [current webpage](https://matek.hu/zoltan/xaos). +Also, you can try a simplified version of the XaoS zooming algorithm online using +[XaoS.js](https://xaos-project.github.io/XaoSjs/). -XaoS is based on [Qt](http://www.qt.io), and has been tested on Linux, Mac, and Windows. It +XaoS is based on [Qt](http://www.qt.io), and has been tested on Windows, Mac and Linux. It should also work on any other platform supporting Qt Widgets, like the BSDs. -- Original authors: [Jan Hubička](http://www.ucw.cz/~hubicka/) and [Thomas Marsh](https://www.linkedin.com/in/thomasmarsh). -- Current maintainers: [J.B. Langston](https://www.linkedin.com/in/jblangston/) and [Zoltán Kovács](https://sites.google.com/site/kovzol/). +- Original authors: [Jan Hubička](https://www.ucw.cz/~hubicka/) and [Thomas Marsh](https://www.linkedin.com/in/thomasmarsh). +- Current maintainers: [J.B. Langston](https://www.linkedin.com/in/jblangston/) and [Zoltán Kovács](https://matek.hu/zoltan). - See [CREDITS](CREDITS.md) for a complete list of contributors. Project Resources @@ -48,7 +50,7 @@ or implement a new feature yourself, pull requests are very welcome. License ------- -Copyright © 1996-2020 XaoS Contributors +Copyright © 1996-2023 XaoS Contributors This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/XaoS.pro b/XaoS.pro index 95edb016..405d0354 100644 --- a/XaoS.pro +++ b/XaoS.pro @@ -2,8 +2,10 @@ # Project created by QtCreator 2009-10-29T19:21:55 # ------------------------------------------------- -lessThan(QT_MAJOR_VERSION, 5): error("requires Qt >= 5") -lessThan(QT_MINOR_VERSION, 7): error("requires Qt >= 5.7") +lessThan(QT_MAJOR_VERSION, 6): error("requires Qt >= 6") +wasm { +lessThan(QT_MINOR_VERSION, 5): error("requires Qt >= 6.5.2") +} TEMPLATE = app @@ -49,15 +51,19 @@ isEmpty(QMAKE_LRELEASE) { } unix { !exists($$QMAKE_LRELEASE) { QMAKE_LRELEASE = lrelease-qt5 } + !exists($$QMAKE_LRELEASE) { QMAKE_LRELEASE = lrelease } } else { !exists($$QMAKE_LRELEASE) { QMAKE_LRELEASE = lrelease } } } CONFIG += optimize_full +CONFIG += c++11 + QMAKE_CXXFLAGS += -ffast-math QMAKE_CFLAGS += -ffast-math +QMAKE_CXXFLAGS += -fpermissive RESOURCES += XaoS.qrc DESTDIR = $$PWD/bin @@ -75,6 +81,11 @@ isEmpty(PREFIX) { PREFIX = /usr/local } DEFINES += DATAPATH=\\\"$$PREFIX/share/XaoS\\\" +wasm{ + QMAKE_LFLAGS += --preload-file $$PWD/examples@$$DATAPATH/examples + QMAKE_LFLAGS += --preload-file $$PWD/catalogs@$$DATAPATH/catalogs + QMAKE_LFLAGS += --preload-file $$PWD/tutorial@$$DATAPATH/tutorial +} executable.files = bin/xaos executable.path = $$PREFIX/bin examples.path = $$PREFIX/share/XaoS/examples diff --git a/src/engine/fractal.cpp b/src/engine/fractal.cpp index 67b78b34..41abe5f5 100644 --- a/src/engine/fractal.cpp +++ b/src/engine/fractal.cpp @@ -55,7 +55,7 @@ static void recalc_view(fractal_context *c) number_t xs = c->s.rr, ys = c->s.ri * c->windowwidth / c->windowheight, xc = c->s.cr, yc = c->s.ci, size; precalculate_rotation(c); - rotate(*c, xc, yc); + my_rotate(*c, xc, yc); /*assert(c->s.rr >= 0); assert(c->s.ri >= 0); */ diff --git a/src/include/config.h b/src/include/config.h index 2f0e9477..52ad5fdf 100644 --- a/src/include/config.h +++ b/src/include/config.h @@ -2,7 +2,7 @@ #define CONFIG_H // XaoS release -#define XaoS_VERSION "4.2.1" +#define XaoS_VERSION "4.3" // URLs #define HELP_URL "https://github.com/xaos-project/XaoS/wiki" diff --git a/src/include/fractal.h b/src/include/fractal.h index e844123d..0ed0b03f 100644 --- a/src/include/fractal.h +++ b/src/include/fractal.h @@ -114,7 +114,7 @@ struct symmetryinfo2 { #define BTRACEOK \ ((cformula.flags & (2 << cfractalc.mandelbrot)) && \ !cfractalc.incoloringmode && cfractalc.coloringmode != 7) -#define rotate(f, x, y) \ +#define my_rotate(f, x, y) \ { \ number_t tmp; \ tmp = (x) * (f).cos - (y) * (f).sin; \ diff --git a/src/ui-hlp/menu.cpp b/src/ui-hlp/menu.cpp index f4628849..ebfb73e8 100644 --- a/src/ui-hlp/menu.cpp +++ b/src/ui-hlp/menu.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "filter.h" #include "config.h" @@ -799,7 +800,7 @@ static void uih_loadgpl(struct uih_context *uih, xio_constpath d) } for(int i = 4; i < 35; i++) { - QStringList currcolors = colorvals[i].split(QRegExp("\\s+")); + QStringList currcolors = colorvals[i].split(QRegularExpression("\\s+")); int r = currcolors[0].toInt(); int g = currcolors[1].toInt(); int b = currcolors[2].toInt(); @@ -1239,10 +1240,14 @@ void uih_registermenus_i18n(void) MENUFLAG_INTERRUPT | MENUFLAG_NOPLAY, uih_playfile, playdialog); MENUSEPARATOR_I("file"); + + // Rendering activities in the WebAssembly version make little sense, and therefore not supported at the moment: +#ifndef __wasm MENUDIALOG_I("file", NULL, TR("Menu", "Render"), "renderanim", UI, uih_render, uih_renderdialog); MENUDIALOG_I("file", NULL, TR("Menu", "Render Image"), "renderimg", UI, uih_renderimg, uih_renderimgdialog); +#endif MENUSEPARATOR_I("file"); MENUNOP_I("file", NULL, TR("Menu", "Load random example"), "loadexample", MENUFLAG_INTERRUPT, uih_loadexample); diff --git a/src/ui-hlp/play.cpp b/src/ui-hlp/play.cpp index 7c78f1fb..27f19bca 100644 --- a/src/ui-hlp/play.cpp +++ b/src/ui-hlp/play.cpp @@ -173,7 +173,7 @@ void uih_update_lines(uih_context *c) case 2: x = l->x1; y = l->y1; - rotate(*(c->fcontext), x, y); + my_rotate(*(c->fcontext), x, y); x = (x - c->fcontext->rs.nc) / (c->fcontext->rs.mc - c->fcontext->rs.nc) * c->zengine->image->width; @@ -185,7 +185,7 @@ void uih_update_lines(uih_context *c) c->zengine->action->convertup(c->zengine, &x1, &y1); x = l->x2; y = l->y2; - rotate(*(c->fcontext), x, y); + my_rotate(*(c->fcontext), x, y); x = (x - c->fcontext->rs.nc) / (c->fcontext->rs.mc - c->fcontext->rs.nc) * c->zengine->image->width; @@ -224,7 +224,7 @@ void uih_update_lines(uih_context *c) default: x = l->mx1; y = l->my1; - rotate(*(c->fcontext), x, y); + my_rotate(*(c->fcontext), x, y); x = (x - c->fcontext->rs.nc) / (c->fcontext->rs.mc - c->fcontext->rs.nc) * c->zengine->image->width; @@ -236,7 +236,7 @@ void uih_update_lines(uih_context *c) c->zengine->action->convertup(c->zengine, &mx1, &my1); x = l->mx2; y = l->my2; - rotate(*(c->fcontext), x, y); + my_rotate(*(c->fcontext), x, y); x = (x - c->fcontext->rs.nc) / (c->fcontext->rs.mc - c->fcontext->rs.nc) * c->zengine->image->width; diff --git a/src/ui/customdialog.cpp b/src/ui/customdialog.cpp index d2279f98..1f3513bf 100644 --- a/src/ui/customdialog.cpp +++ b/src/ui/customdialog.cpp @@ -16,7 +16,7 @@ QStringList fnames = {}; -QString format(number_t number) +QString CustomDialog::format(number_t number) { char buf[256]; #ifdef USE_FLOAT128 @@ -51,13 +51,13 @@ CustomDialog::CustomDialog(struct uih_context *uih, const menuitem *item, QLineEdit *real = new QLineEdit(format(dialog[i].deffloat), this); QFontMetrics metric(real->font()); - real->setMinimumWidth(metric.width(real->text()) * 1.1); + real->setMinimumWidth(metric.horizontalAdvance(real->text()) * 1.1); real->setObjectName(label + "real"); // real->setValidator(new QDoubleValidator(real)); QLineEdit *imag = new QLineEdit(format(dialog[i].deffloat2), this); imag->setObjectName(label + "imag"); - imag->setMinimumWidth(metric.width(imag->text()) * 1.1); + imag->setMinimumWidth(metric.horizontalAdvance(imag->text()) * 1.1); // imag->setValidator(new QDoubleValidator(imag)); QBoxLayout *layout = new QBoxLayout(QBoxLayout::LeftToRight); @@ -74,7 +74,7 @@ CustomDialog::CustomDialog(struct uih_context *uih, const menuitem *item, QLineEdit *filename = new QLineEdit(dialog[i].defstr, this); QFontMetrics metric(filename->font()); - filename->setMinimumWidth(metric.width(filename->text()) * 1.1); + filename->setMinimumWidth(metric.horizontalAdvance(filename->text()) * 1.1); filename->setObjectName(label); QToolButton *chooser = new QToolButton(this); @@ -250,7 +250,7 @@ CustomDialog::CustomDialog(struct uih_context *uih, const menuitem *item, field->setText(dialog[i].defstr); } QFontMetrics metric(field->font()); - field->setMinimumWidth(metric.width(field->text()) * 1.1); + field->setMinimumWidth(metric.horizontalAdvance(field->text()) * 1.1); formLayout->addRow(label, field); } } diff --git a/src/ui/customdialog.h b/src/ui/customdialog.h index 878ebb90..c9ac2136 100644 --- a/src/ui/customdialog.h +++ b/src/ui/customdialog.h @@ -34,6 +34,7 @@ class CustomDialog : public QDialog const menudialog *dialog, QWidget *parent = 0); void accept(); dialogparam *parameters(); + static QString format(number_t number); }; #endif // CUSTOMDIALOG_H diff --git a/src/ui/fractalwidget.cpp b/src/ui/fractalwidget.cpp index 3b5411f7..c367f6a6 100644 --- a/src/ui/fractalwidget.cpp +++ b/src/ui/fractalwidget.cpp @@ -16,7 +16,7 @@ FractalWidget::FractalWidget() setAttribute(Qt::WA_OpaquePaintEvent, true); } -QPoint FractalWidget::mousePosition() { return m_mousePosition; } +QPointF FractalWidget::mousePosition() { return m_mousePosition; } void FractalWidget::setImage(struct image *image) { m_image = image; } @@ -75,6 +75,6 @@ void FractalWidget::mouseMoveEvent(QMouseEvent *event) void FractalWidget::wheelEvent(QWheelEvent *event) { - m_mousePosition = event->pos(); + m_mousePosition = event->position(); event->ignore(); } diff --git a/src/ui/fractalwidget.h b/src/ui/fractalwidget.h index 6b572559..26eaab42 100644 --- a/src/ui/fractalwidget.h +++ b/src/ui/fractalwidget.h @@ -19,7 +19,7 @@ class FractalWidget : public QWidget private: struct image *m_image = NULL; QSize m_sizeHint; - QPoint m_mousePosition = QPoint(0, 0); + QPointF m_mousePosition = QPointF(0.0, 0.0); protected: void mouseMoveEvent(QMouseEvent *event); @@ -35,7 +35,7 @@ class FractalWidget : public QWidget public: FractalWidget(); QSize sizeHint() const; - QPoint mousePosition(); + QPointF mousePosition(); void setImage(struct image *image); }; diff --git a/src/ui/image_qt.cpp b/src/ui/image_qt.cpp index a57ab503..dce32620 100644 --- a/src/ui/image_qt.cpp +++ b/src/ui/image_qt.cpp @@ -63,7 +63,7 @@ int xtextwidth(struct image */*image*/, void *font, const char *text) line[pos] = '\0'; QFontMetrics metrics(getFont(font)); - return metrics.width(line) + 1; + return metrics.horizontalAdvance(line) + 1; } int xtextheight(struct image */*image*/, void *font) @@ -75,7 +75,7 @@ int xtextheight(struct image */*image*/, void *font) int xtextcharw(struct image */*image*/, void *font, const char c) { QFontMetrics metrics(getFont(font)); - return metrics.width(c); + return metrics.horizontalAdvance(c); } // Saves image as png with xpf chunk data @@ -98,7 +98,7 @@ const char* readpng(xio_constpath filename) const QImage xaos_image = reader.read(); QString xpf_chunk = xaos_image.text("Metadata"); const char *xpf_data = NULL; - if(xpf_chunk != NULL or !xpf_chunk.isEmpty()) + if(xpf_chunk != QString() or !xpf_chunk.isEmpty()) xpf_data = mystrdup(xpf_chunk.toStdString().c_str()); return xpf_data; } diff --git a/src/ui/main.cpp b/src/ui/main.cpp index daa239ec..66316d85 100644 --- a/src/ui/main.cpp +++ b/src/ui/main.cpp @@ -321,12 +321,12 @@ static void ui_about(struct uih_context *uih) QSysInfo::kernelType() + " " + // QSysInfo::kernelVersion() + " " // QSysInfo::buildAbi() + " " + - QSysInfo::buildCpuArchitecture() + + QSysInfo::buildCpuArchitecture() + ", Qt " + QT_VERSION_STR + ")" "
" "Fast interactive real-time fractal zoomer/morpher

" "Original Authors: Jan Hubička and Thomas Marsh
" - "Copyright © 1996-2020 XaoS Contributors
" + "Copyright © 1996-2023 XaoS Contributors
" "
" "This program is free software; you can redistribute it and/or modify " "it under the terms of the GNU General Public License as published by " @@ -363,12 +363,16 @@ void uih_setlanguage(uih_context *c, int l) settings.setValue("MainWindow/language", lang2(l)); QMessageBox msgBox; msgBox.setText(TR("Message", "XaoS must restart to change the language.")); +#ifndef __wasm msgBox.setInformativeText(TR("Message", "Do you want to quit now?")); msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); +#endif int ret = msgBox.exec(); +#ifndef __wasm if (ret == QMessageBox::Yes) { exit(0); } +#endif } #ifndef Q_OS_MAC @@ -477,8 +481,10 @@ static void ui_registermenus_i18n(void) { int no_menuitems_i18n = ui_no_menuitems_i18n; /* This variable must be local. */ +#ifndef __wasm MENUINT_I("file", NULL, TR("Menu", "Quit"), "quit", MENUFLAG_INTERRUPT | MENUFLAG_ATSTARTUP, ui_quit, UI); +#endif MENUNOP_I("ui", NULL, TR("Menu", "Message Font..."), "font", UI, ui_font); MENUNOP_I("uia", NULL, TR("Menu", "Message Font..."), "font", UI, ui_font); diff --git a/src/ui/mainwindow.cpp b/src/ui/mainwindow.cpp index 3bfb4fd8..76571823 100644 --- a/src/ui/mainwindow.cpp +++ b/src/ui/mainwindow.cpp @@ -1,5 +1,7 @@ #include #include +#include +#include #include "mainwindow.h" #include "fractalwidget.h" @@ -414,27 +416,34 @@ xio_pathdata configfile; void MainWindow::eventLoop() { - int inmovement = 1; - int time; - for (;;) { + QTimer eventTimer; + eventTimer.setTimerType(Qt::PreciseTimer); + + connect(&eventTimer, &QTimer::timeout, this, [=]() { + int inmovement = 1; + widget->setCursor(uih->play ? Qt::ForbiddenCursor : Qt::CrossCursor); + if (uih->display) { uih_prepare_image(uih); uih_updatestatus(uih); widget->repaint(); showStatus(""); } - if ((time = tl_process_group(syncgroup, NULL)) != -1) { + + int time = tl_process_group(syncgroup, nullptr); + if (time != -1) { if (!inmovement && !uih->inanimation) { if (time > 1000000 / 50) time = 1000000 / 50; if (time > delaytime) { - tl_sleep(time - delaytime); + QThread::usleep(time - delaytime); tl_update_time(); } } inmovement = 1; } + if (delaytime || maxframerate) { tl_update_time(); time = tl_lookup_timer(loopt); @@ -443,18 +452,26 @@ void MainWindow::eventLoop() if (time < delaytime) time = delaytime; if (time) { - tl_sleep(time); + QThread::usleep(time); tl_update_time(); } } + processQueue(); processEvents(!inmovement && !uih->inanimation); inmovement = 0; + if (shouldResize) { resizeImage(widget->size().width(), widget->size().height()); shouldResize = false; } - } + }); + + // Start the event timer + eventTimer.start(0); + + // Enter the Qt event loop + QCoreApplication::exec(); } void MainWindow::updateMenus(const char *name) @@ -536,15 +553,20 @@ static void ui_message(struct uih_context *uih) void MainWindow::chooseFont() { - bool ok; - messageFont = QFontDialog::getFont(&ok, messageFont, this); - if (ok) { - QSettings settings; - settings.setValue("MainWindow/messageFontFamily", messageFont.family()); - settings.setValue("MainWindow/messageFontSize", - messageFont.pointSize()); - uih->font = &messageFont; - } + QFontDialog *fontDialog = new QFontDialog(this); + QSettings settings; + QFont qfont(settings.value("MainWindow/messageFontFamily").toString(), + settings.value("MainWindow/messageFontSize").toInt()); + fontDialog->setCurrentFont(qfont); + connect(fontDialog, &QFontDialog::fontSelected, + [=](const QFont &messageFont) { + QSettings settings; + settings.setValue("MainWindow/messageFontFamily", messageFont.family()); + settings.setValue("MainWindow/messageFontSize", + messageFont.pointSize()); + uih->font = (void *) &messageFont; + }); + fontDialog->open(); } MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) @@ -808,6 +830,54 @@ void MainWindow::updateMenuCheckmarks() } } +struct palette *gradientpal; QSpinBox *seedno, *algono, *shiftno; QLabel *img; + +void MainWindow::updateVisualiser() +{ + // Get updated Colors + int colors[101][3]; + getPaletteColor(gradientpal, seedno->value(), + algono->value()-1 < 0? 0:algono->value()-1, shiftno->value(), colors); + + // Load Curve + QImage palImage(100, 1, QImage::Format_RGB32); + + // Fill Curve + for(int i=0;i<100;i++) { + QRgb value = qRgb(colors[i][0], colors[i][1], colors[i][2]); + palImage.setPixelColor(i, 0, value); + } + + // Save Result + QPixmap newImage = QPixmap::fromImage(palImage.scaled(algono->width(), + algono->height())); + img->setPixmap(newImage); +} + +unsigned char newColors[32][3]; + +void MainWindow::colorPicker() +{ + QPushButton* button = qobject_cast(sender()); + int idx = button->objectName().toInt(); + QColorDialog* qColorDialog = new QColorDialog(this); + QColor currentColor(newColors[idx][0], newColors[idx][1], newColors[idx][2]); + qColorDialog->setCurrentColor(currentColor); + qColorDialog->setModal(false); + connect(qColorDialog, &QColorDialog::colorSelected, qColorDialog, + [=](const QColor &color) { + QPalette pal = button->palette(); + button->setAutoFillBackground(true); + pal.setColor(QPalette::Button, color); + button->setPalette(pal); + button->update(); + newColors[idx][0] = color.red(); + newColors[idx][1] = color.green(); + newColors[idx][2] = color.blue(); + }); + qColorDialog->open(); +} + void MainWindow::showDialog(const char *name) { const menuitem *item = menu_findcommand(name); @@ -830,10 +900,33 @@ void MainWindow::showDialog(const char *name) QString fileLocation = settings.value("MainWindow/lastFileLocation", QDir::homePath()) .toString(); - QString fileName; - if (dialog[0].type == DIALOG_IFILE) - fileName = QFileDialog::getOpenFileName(this, item->name, - fileLocation, filter); + if (dialog[0].type == DIALOG_IFILE) { + // fileName = QFileDialog::getOpenFileName(this, item->name, fileLocation, filter); + QFileDialog *qFileDialog = new QFileDialog(this); + // qFileDialog->setWindowTitle(dialog->question); + qFileDialog->setWindowTitle(item->name); + qFileDialog->setDirectory(fileLocation); + qFileDialog->setNameFilter(filter); + connect(qFileDialog, &QFileDialog::fileSelected, + [=](const QString &value) { + QString fileName = value; + if (!fileName.isNull()) { + QString ext = "." + QFileInfo(dialog[0].defstr).suffix(); + + if (!fileName.endsWith(".xpf") and !fileName.endsWith(".png") + and !fileName.endsWith(ext)) + fileName += ext; + + dialogparam *param = (dialogparam *)malloc(sizeof(dialogparam)); + param->dstring = strdup(fileName.toUtf8()); + menuActivate(item, param); + QSettings settings; + settings.setValue("MainWindow/lastFileLocation", + QFileInfo(fileName).absolutePath()); + } + }); + qFileDialog->open(); + } else if (dialog[0].type == DIALOG_OFILE) { char defname[256]; strcpy(defname, @@ -841,37 +934,458 @@ void MainWindow::showDialog(const char *name) char *split = strchr(defname, '*'); *split = 0; strcpy(defname, xio_getfilename(defname, split + 1)); - fileName = - QFileDialog::getSaveFileName(this, item->name, defname, filter); + // fileName = QFileDialog::getSaveFileName(this, item->name, defname, filter); + QFileDialog *qFileDialog = new QFileDialog(this); + qFileDialog->setWindowTitle(item->name); + qFileDialog->setDirectory(fileLocation); + qFileDialog->setNameFilter(filter); + qFileDialog->setAcceptMode(QFileDialog::AcceptSave); + connect(qFileDialog, &QFileDialog::fileSelected, + [=](const QString &value) { + QString fileName = value; + if (!fileName.isNull()) { + QString ext = "." + QFileInfo(dialog[0].defstr).suffix(); + + if (!fileName.endsWith(".xpf") and !fileName.endsWith(".png") + and !fileName.endsWith(ext)) + fileName += ext; + + dialogparam *param = (dialogparam *)malloc(sizeof(dialogparam)); + param->dstring = strdup(fileName.toUtf8()); + menuActivate(item, param); + QSettings settings; + settings.setValue("MainWindow/lastFileLocation", + QFileInfo(fileName).absolutePath()); + } + }); + qFileDialog->open(); } + } else { - if (!fileName.isNull()) { - QString ext = "." + QFileInfo(dialog[0].defstr).suffix(); - - if (!fileName.endsWith(".xpf") and !fileName.endsWith(".png") - and !fileName.endsWith(ext)) - fileName += ext; + dialogparam *param = (dialogparam *)malloc(sizeof(dialogparam)); + + switch (dialog->type) { + case DIALOG_INT: + case DIALOG_FLOAT: + case DIALOG_STRING: + case DIALOG_KEYSTRING: + { + QInputDialog *qInputDialog = new QInputDialog(this); + qInputDialog->setLabelText(dialog->question); + qInputDialog->setWindowTitle(item->name); + switch (dialog->type) { + case DIALOG_INT: + { + qInputDialog->setIntMaximum(1000000); + qInputDialog->setIntValue(dialog->defint); + connect(qInputDialog, &QInputDialog::intValueSelected, qInputDialog, + [=](const unsigned long int &value) { + param->dint = value; + menuActivate(item, param); + }); + break; + } + case DIALOG_FLOAT: + { + qInputDialog->setDoubleMaximum(1000000); + qInputDialog->setDoubleValue(dialog->deffloat); + connect(qInputDialog, &QInputDialog::doubleValueSelected, qInputDialog, + [=](const double &value) { + param->number = value; + menuActivate(item, param); + }); + break; + } + case DIALOG_STRING: + { + qInputDialog->setTextValue(dialog->defstr); + connect(qInputDialog, &QInputDialog::textValueSelected, qInputDialog, + [=](const QString &text) { + param->dstring = strdup((char *) text.toStdString().c_str()); + menuActivate(item, param); + }); + break; + } + case DIALOG_KEYSTRING: + { + qInputDialog->setTextValue(dialog->defstr); + connect(qInputDialog, &QInputDialog::textValueSelected, qInputDialog, + [=](const QString &text) { + param->dstring = strdup((char *) text.toStdString().c_str()); + menuActivate(item, param); + }); + break; + } + } + qInputDialog->open(); + break; + } + case DIALOG_COORD: + { + QDialog *qDialog = new QDialog(this); + qDialog->setWindowTitle(item->name); + QBoxLayout *dialogLayout = new QBoxLayout(QBoxLayout::TopToBottom, qDialog); + QFormLayout *formLayout = new QFormLayout(); + QString label(dialog->question); + QLineEdit *real = new QLineEdit(CustomDialog::format(dialog->deffloat), qDialog); + QFontMetrics metric(real->font()); + real->setMinimumWidth(metric.horizontalAdvance(real->text()) * 1.1); + real->setObjectName("real"); + QLineEdit *imag = new QLineEdit(CustomDialog::format(dialog->deffloat2), qDialog); + imag->setObjectName("imag"); + imag->setMinimumWidth(metric.horizontalAdvance(imag->text()) * 1.1); + // imag->setValidator(new QDoubleValidator(imag)); + QBoxLayout *layout = new QBoxLayout(QBoxLayout::LeftToRight); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(real); + layout->addWidget(new QLabel("+", qDialog)); + layout->addWidget(imag); + layout->addWidget(new QLabel("i", qDialog)); + formLayout->addRow(label, layout); + dialogLayout->addLayout(formLayout); + QDialogButtonBox *buttonBox = + new QDialogButtonBox((QDialogButtonBox::Ok | QDialogButtonBox::Cancel), + Qt::Horizontal, qDialog); + connect(buttonBox, SIGNAL(accepted()), qDialog, SLOT(accept())); + connect(buttonBox, SIGNAL(rejected()), qDialog, SLOT(reject())); + connect(real, SIGNAL(textEdited(QString)), qDialog, SLOT(update())); + dialogLayout->addWidget(buttonBox); + qDialog->setLayout(dialogLayout); + connect(qDialog, &QDialog::accepted, qDialog, + [=](void){ + QLineEdit *real = qDialog->findChild("real"); + param->dcoord[0] = real->text().toDouble(); + QLineEdit *imag = qDialog->findChild("imag"); + param->dcoord[1] = imag->text().toDouble(); + menuActivate(item, param); + }); + qDialog->adjustSize(); // this is sometimes too high in WASM, FIXME, maybe Qt6 bug? + qDialog->open(); + break; + } + case DIALOG_PALSLIDER: + { + QDialog *qDialog = new QDialog(this); + qDialog->setWindowTitle(item->name); + QBoxLayout *dialogLayout = new QBoxLayout(QBoxLayout::TopToBottom, qDialog); + gradientpal = clonepalette(uih->image->palette); + uih_context *palcontext; + palcontext = uih; + // 3 inputs decide color, Algorithm Number, Seed and shift + // For Algorithm number + QSlider *seedslider, *algoslider, *shiftslider; + algono = new QSpinBox(this); + QString label(dialog->question); + algono->setObjectName(label + "algono"); + algono->setValue(palcontext->palettetype); + algono->setRange(1, 3); + + // Algo Slider + algoslider = new QSlider(Qt::Horizontal, qDialog); + algoslider->setObjectName(label + "-algono"); + algoslider->setRange(1, PALGORITHMS); + algoslider->setValue(algono->value()); + // algoslider->setMinimumWidth(this->width()*2); + + // For Seed Number + seedno = new QSpinBox(qDialog); + seedno->setObjectName(label + "seedno"); + seedno->setRange(0, gradientpal->size); + seedno->setValue(palcontext->paletteseed); + + // Seed Slider + seedslider = new QSlider(Qt::Horizontal, qDialog); + seedslider->setObjectName(label + "-seedno"); + seedslider->setRange(0, gradientpal->size); + seedslider->setValue(seedno->value()); + + // For Shift Number + shiftno = new QSpinBox(this); + shiftno->setObjectName(label + "shiftno"); + shiftno->setRange(0, gradientpal->size); + shiftno->setValue(palcontext->paletteshift + palcontext->manualpaletteshift); + + // Shift Slider + shiftslider = new QSlider(Qt::Horizontal, qDialog); + shiftslider->setObjectName(label + "-shiftno"); + shiftslider->setRange(0, gradientpal->size); + shiftslider->setValue(shiftno->value()); + + // Add them to Layout + QFormLayout *formLayout = new QFormLayout(); + formLayout->addRow("Algorithm", algono); + formLayout->addWidget(algoslider); + formLayout->addRow("Seed", seedno); + formLayout->addWidget(seedslider); + formLayout->addRow("Shift", shiftno); + formLayout->addWidget(shiftslider); + + img = new QLabel(qDialog); + img->setScaledContents(true); + formLayout->addRow(img); + updateVisualiser(); + + connect(algono,SIGNAL(valueChanged(int)), algoslider, SLOT(setValue(int))); + connect(algoslider, SIGNAL(valueChanged(int)), algono, SLOT(setValue(int))); + connect(algono, SIGNAL(valueChanged(int)), this, SLOT(updateVisualiser())); + connect(seedno,SIGNAL(valueChanged(int)), seedslider, SLOT(setValue(int))); + connect(seedslider, SIGNAL(valueChanged(int)), seedno, SLOT(setValue(int))); + connect(seedno, SIGNAL(valueChanged(int)), this, SLOT(updateVisualiser())); + connect(shiftno,SIGNAL(valueChanged(int)), shiftslider, SLOT(setValue(int))); + connect(shiftslider, SIGNAL(valueChanged(int)), shiftno, SLOT(setValue(int))); + connect(shiftno, SIGNAL(valueChanged(int)), this, SLOT(updateVisualiser())); + + dialogLayout->addLayout(formLayout); + QDialogButtonBox *buttonBox = + new QDialogButtonBox((QDialogButtonBox::Ok | QDialogButtonBox::Cancel), + Qt::Horizontal, qDialog); + + connect(buttonBox, SIGNAL(accepted()), qDialog, SLOT(accept())); + connect(buttonBox, SIGNAL(rejected()), qDialog, SLOT(reject())); + + dialogLayout->addWidget(buttonBox); + qDialog->setLayout(dialogLayout); + + connect(qDialog, &QDialog::accepted, qDialog, + [=](void){ + QSlider *algo = qDialog->findChild(label + "-algono"); + palcontext->palettetype = algo->sliderPosition(); + palcontext->manualpaletteshift = 0; + QSlider *seed = qDialog->findChild(label + "-seedno"); + palcontext->paletteseed = seed->sliderPosition(); + QSlider *shift = qDialog->findChild(label + "-shiftno"); + palcontext->paletteshift = shift->sliderPosition(); + param->dint = 1; + menuActivate(item, param); + destroypalette(gradientpal); + }); + qDialog->open(); + break; + } + case DIALOG_PALPICKER: + { + QDialog *qDialog = new QDialog(this); + qDialog->setWindowTitle(item->name); + QBoxLayout *dialogLayout = new QBoxLayout(QBoxLayout::TopToBottom, qDialog); + + uih_context *palcontext; + palcontext = uih; + getDEFSEGMENTColor(newColors); + + QList< QPushButton* > buttons; + QBoxLayout *layout1 = new QBoxLayout(QBoxLayout::LeftToRight); + QBoxLayout *layout2 = new QBoxLayout(QBoxLayout::LeftToRight); + QBoxLayout *layout3 = new QBoxLayout(QBoxLayout::LeftToRight); + for(auto bidx = 0; bidx < 31; ++bidx ) { + auto button = new QPushButton{ QString::number(bidx) }; + button->setObjectName(QString::number(bidx)); + QColor color(newColors[bidx][0], newColors[bidx][1], newColors[bidx][2]); + QPalette pal = button->palette(); + button->setAutoFillBackground(true); + pal.setColor(QPalette::Button, color); + button->setPalette(pal); + button->update(); + buttons << button; + if(bidx <= 10) + layout1->addWidget(button); + else if(bidx>10 and bidx <= 20) + layout2->addWidget(button); + else + layout3->addWidget(button); + + connect(button, SIGNAL(clicked()), this, SLOT(colorPicker())); + } + QFormLayout *formLayout = new QFormLayout(); + formLayout->addRow(layout1); + formLayout->addRow(layout2); + formLayout->addRow(layout3); + dialogLayout->addLayout(formLayout); + QDialogButtonBox *buttonBox = + new QDialogButtonBox((QDialogButtonBox::Ok | QDialogButtonBox::Cancel), + Qt::Horizontal, qDialog); + + connect(buttonBox, SIGNAL(accepted()), qDialog, SLOT(accept())); + connect(buttonBox, SIGNAL(rejected()), qDialog, SLOT(reject())); + + dialogLayout->addWidget(buttonBox); + qDialog->setLayout(dialogLayout); + + connect(qDialog, &QDialog::accepted, qDialog, + [=](void){ + mkcustompalette(palcontext->image->palette, newColors); + menuActivate(item, param); + }); + qDialog->open(); + break; + } + case DIALOG_LIST: // This is used only in Formulas/UserFormulas + { + QDialog *qDialog = new QDialog(this); + qDialog->setWindowTitle(item->name); + QBoxLayout *dialogLayout = new QBoxLayout(QBoxLayout::TopToBottom, qDialog); + + QComboBox *list = new QComboBox(this); + QString label(dialog->question); + list->setObjectName(label); + list->setEditable(true); + list->addItem(dialog->defstr); + list->setObjectName(label + "-data"); + + QSettings settings; + QStringList formulas = settings.value("Formulas/UserFormulas").toStringList(); + + for (int j = 0; j < formulas.count(); j++) { + bool found = false; + for (int i = 0; i < list->count(); i++) { + if (QString::compare(list->itemText(i), formulas[j], Qt::CaseSensitive) == 0) { + found = true; + } + } + if (!found) + list->addItem(formulas[j]); + } - dialogparam *param = (dialogparam *)malloc(sizeof(dialogparam)); - param->dstring = strdup(fileName.toUtf8()); - menuActivate(item, param); - settings.setValue("MainWindow/lastFileLocation", - QFileInfo(fileName).absolutePath()); + // list->addItems(formulas); + + QFormLayout *formLayout = new QFormLayout(); + formLayout->addRow(label, list); + dialogLayout->addLayout(formLayout); + + QDialogButtonBox *buttonBox = + new QDialogButtonBox((QDialogButtonBox::Ok | QDialogButtonBox::Cancel), + Qt::Horizontal, qDialog); + + connect(buttonBox, SIGNAL(accepted()), qDialog, SLOT(accept())); + connect(buttonBox, SIGNAL(rejected()), qDialog, SLOT(reject())); + dialogLayout->addWidget(buttonBox); + qDialog->setLayout(dialogLayout); + qDialog->adjustSize(); // this is sometimes too high in WASM, FIXME, maybe Qt6 bug? + + connect(qDialog, &QDialog::accepted, qDialog, + [=](void){ + QComboBox *selected = qDialog->findChild(label + "-data"); + param->dstring = strdup(selected->currentText().toUtf8()); + menuActivate(item, param); + }); + qDialog->open(); + break; + } + case DIALOG_CHOICE: + { + QDialog *qDialog = new QDialog(this); + qDialog->setWindowTitle(item->name); + QBoxLayout *dialogLayout = new QBoxLayout(QBoxLayout::TopToBottom, qDialog); + + QComboBox *combo = new QComboBox(this); + QString label(dialog->question); + combo->setObjectName(label); + + const char **str = (const char **)dialog->defstr; + for (int j = 0; str[j] != NULL; j++) + combo->addItem(str[j]); + combo->setCurrentIndex(dialog->defint); + QFormLayout *formLayout = new QFormLayout(); + formLayout->addRow(label, combo); + dialogLayout->addLayout(formLayout); + + QDialogButtonBox *buttonBox = + new QDialogButtonBox((QDialogButtonBox::Ok | QDialogButtonBox::Cancel), + Qt::Horizontal, qDialog); + + connect(buttonBox, SIGNAL(accepted()), qDialog, SLOT(accept())); + connect(buttonBox, SIGNAL(rejected()), qDialog, SLOT(reject())); + dialogLayout->addWidget(buttonBox); + qDialog->setLayout(dialogLayout); + qDialog->adjustSize(); // this is sometimes too high in WASM, FIXME, maybe Qt6 bug? + + connect(qDialog, &QDialog::accepted, qDialog, + [=](void){ + QComboBox *selected = qDialog->findChild(label); + param->dint = selected->currentIndex(); + menuActivate(item, param); + }); + qDialog->open(); + break; + } + default: + { + CustomDialog customDialog(uih, item, dialog, this); + if (customDialog.exec() == QDialog::Accepted) + menuActivate(item, customDialog.parameters()); + } } - } else { - CustomDialog customDialog(uih, item, dialog, this); - if (customDialog.exec() == QDialog::Accepted) - menuActivate(item, customDialog.parameters()); } } +#ifdef __wasm +#define STATUS_VIA_STDOUT +//#define STATUS_VIA_PROGRESSBAR +#else +#define STATUS_VIA_PROGRESSBAR +// #define STATUS_VIA_WINDOWTITLE +#endif + +QProgressDialog *qProgressDialog; +int progress = 0; void MainWindow::showStatus(const char *text) { +// This is not working properly, maybe because of a missing QTimer event/setting. +#ifdef STATUS_VIA_UIH_MESSAGE + if (uih != NULL) { + uih_message(uih, text); + uih_updatestatus(uih); + } +#endif + +#ifdef STATUS_VIA_WINDOWTITLE if (strlen(text)) setWindowTitle( QCoreApplication::applicationName().append(" - ").append(text)); else setWindowTitle(QCoreApplication::applicationName()); +#endif + +#ifdef STATUS_VIA_STDOUT + std::cout << "STATUS: " << text << "\n"; +#endif + +// This feature is experimental. It works natively but not in WASM. +#ifdef STATUS_VIA_PROGRESSBAR + bool newProgress = (qProgressDialog == NULL); + if (QString(text) == "") { + if (!newProgress) { + qProgressDialog->close(); + progress = 0; + return; + } + } + + if (newProgress) { + qProgressDialog = new QProgressDialog(this); + } else { + qProgressDialog->setValue(progress); + qProgressDialog->setMinimumDuration(0); + QString t = QString(text).trimmed(); + if (t.endsWith("%")) { + progress = t.right(6).left(5).toDouble(); // save the percentage + // std::cout << "t=" << t.toStdString() << " progress=" << progress << "\n"; + t = t.left(t.length()-6); // remove the percentage + } + + qProgressDialog->setCancelButton(NULL); + qProgressDialog->setWindowTitle(t); + if (progress < 100) { + progress++; + } + else { + progress=0; + } + } + + if (newProgress) { + qProgressDialog->show(); + } +#endif } int MainWindow::mouseButtons() @@ -885,7 +1399,7 @@ int MainWindow::mouseButtons() // Otherwise, mouse buttons map normally if (m_mouseButtons & Qt::LeftButton) mouseButtons |= BUTTON1; - if (m_mouseButtons & Qt::MidButton) + if (m_mouseButtons & Qt::MiddleButton) mouseButtons |= BUTTON2; if (m_mouseButtons & Qt::RightButton) mouseButtons |= BUTTON3; @@ -920,7 +1434,7 @@ void MainWindow::mouseReleaseEvent(QMouseEvent *event) void MainWindow::wheelEvent(QWheelEvent *event) { - m_mouseWheel = event->delta(); + m_mouseWheel = event->angleDelta().y(); clock_gettime(CLOCK_REALTIME, &wheeltimer); } diff --git a/src/ui/mainwindow.h b/src/ui/mainwindow.h index 149e1634..3215e07d 100644 --- a/src/ui/mainwindow.h +++ b/src/ui/mainwindow.h @@ -13,10 +13,10 @@ class MainWindow : public QMainWindow { Q_OBJECT private: - Qt::MouseButtons m_mouseButtons = 0; + Qt::MouseButtons m_mouseButtons = Qt::NoButton; int m_mouseWheel = 0; timespec wheeltimer; - Qt::KeyboardModifiers m_keyboardModifiers = 0; + Qt::KeyboardModifiers m_keyboardModifiers = Qt::NoModifier; int m_keyCombination = 0; bool shouldResize = false; FractalWidget *widget; @@ -66,6 +66,8 @@ class MainWindow : public QMainWindow private slots: void activateMenuItem(); void updateMenuCheckmarks(); + void updateVisualiser(); + void colorPicker(); public: MainWindow(QWidget *parent = 0); diff --git a/src/util/xmenu.cpp b/src/util/xmenu.cpp index 86a8478a..2eb2f382 100644 --- a/src/util/xmenu.cpp +++ b/src/util/xmenu.cpp @@ -3,8 +3,6 @@ #include #include #include "config.h" -#include "filter.h" -#include "fractal.h" #include "ui_helper.h" #include "xerror.h" #include "misc-f.h" @@ -340,12 +338,28 @@ void menu_activate(const menuitem *item, struct uih_context *c, dialogparam *d) // be cast to a valid float parameter which the function // expects. For now I will just special case it because // uimandelbrot is the only menu that does this. - if (!strcmp(item->shortname, "uimandelbrot")) + + // Also, similar problems occur when compiling for WebAssembly, + // but also for uiperturbation and record. So we handle these cases differently. + + if (!strcmp(item->shortname, "uimandelbrot") && + !strcmp(item->shortname, "uiperturbation") && + !strcmp(item->shortname, "record")) ((void (*)(struct uih_context * c, number_t, number_t)) item->function)(c, 0, 0); - else + else { +#ifndef __wasm ((void (*)(struct uih_context * c, dialogparam *)) - item->function)(c, (dialogparam *)NULL); + item->function)(c, (dialogparam *)NULL); +#else + if (strcmp(item->shortname, "uimandelbrot") == 0) + uih_setmandelbrot(c, 1, 0, 0); + if (strcmp(item->shortname, "uiperturbation") == 0) + uih_setperbutation(c, 0, 0); + if (strcmp(item->shortname, "record") == 0) + uih_save_disable(c); +#endif + } } else { const menudialog *di = menu_getdialog(c, item); if (di[0].question == NULL) { diff --git a/src/util/xstdio.cpp b/src/util/xstdio.cpp index 4ed92c66..2c0f7d55 100644 --- a/src/util/xstdio.cpp +++ b/src/util/xstdio.cpp @@ -8,6 +8,12 @@ #include "fractal.h" #include "ui_helper.h" #include "misc-f.h" +#include +#include +#include +#include +#include +#include #ifdef _WIN32 #define strcmp stricmp #include @@ -126,9 +132,30 @@ xio_path xio_getfilename(const char *basename, const char *extension) return (name); } +// Function to get the names of immediate subdirectories under a path +QStringList get_immediate_subdirectory_names(const QString& path) { + QDir directory(path); + QStringList subdirectoryNames; + + QFileInfoList entries = directory.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); + for (int i = 0; i < entries.size(); ++i) { + subdirectoryNames.append(entries.at(i).fileName()); + } + + return subdirectoryNames; +} + +// Function to convert QStringList to char *array +void convertQStringListToArray(const QStringList& stringList, char *array[]) { + for (int i = 0; i < stringList.size(); ++i) { + QByteArray byteArray = stringList.at(i).toLocal8Bit(); + array[i] = strdup(byteArray.constData()); + } +} + xio_file xio_getrandomexample(xio_path name) { - static const char *const paths[] = { + QStringList example_paths_taken = { /*Where examples should be located? */ EXAMPLESPATH, /*Data path when XaoS is properly installed */ "\01" XIO_PATHSEPSTR "examples", @@ -142,6 +169,15 @@ xio_file xio_getrandomexample(xio_path name) /*XaoS was started from bin directory in source tree */ XIO_EMPTYPATH, /*Oops...it's not. Try current directory */ }; + + // Select a random sub dir and then random example to fix load random example issue + QStringList sub_names = get_immediate_subdirectory_names(".." XIO_PATHSEPSTR "examples"); + int randomIndex = QRandomGenerator::global()->bounded(sub_names.length()); + QString randomElement = sub_names.at(randomIndex); + example_paths_taken.append(".." XIO_PATHSEPSTR "examples/"+randomElement); + char *paths[example_paths_taken.size()]; + convertQStringListToArray(example_paths_taken, paths); + int i = -1, p; DIR *d = NULL; xio_file f;