#include "WFPalette.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "qt_helpers.hpp" #include "ui_wf_palette_design_dialog.h" namespace { int constexpr points {256}; struct init { init () { qRegisterMetaTypeStreamOperators ("Colours"); } } static_initaializer; using Colours = WFPalette::Colours; // ensure that palette colours are useable for interpolation Colours make_valid (Colours colours) { if (colours.size () < 2) { // allow single element by starting at black colours.prepend (QColor {0, 0, 0}); } if (1 == colours.size ()) { // allow empty list by using black to white colours.append (QColor {255,255,255}); } if (colours.size () > points) { throw_qstring (QObject::tr ("Too many colours in palette.")); } return colours; } // load palette colours from a file Colours load_palette (QString const& file_name) { Colours colours; QFile file {file_name}; if (file.open (QIODevice::ReadOnly)) { unsigned count {0}; QTextStream in (&file); int line_counter {0}; while (!in.atEnd ()) { auto line = in.readLine(); ++line_counter; if (++count >= points) { throw_qstring (QObject::tr ("Error reading waterfall palette file \"%1:%2\" too many colors.") .arg (file.fileName ()).arg (line_counter)); } auto items = line.split (';'); if (items.size () != 3) { throw_qstring (QObject::tr ("Error reading waterfall palette file \"%1:%2\" invalid triplet.") .arg (file.fileName ()).arg (line_counter)); } bool r_ok, g_ok, b_ok; auto r = items[0].toInt (&r_ok); auto g = items[1].toInt (&g_ok); auto b = items[2].toInt (&b_ok); if (!r_ok || !g_ok || !b_ok || r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255) { throw_qstring (QObject::tr ("Error reading waterfall palette file \"%1:%2\" invalid color.") .arg (file.fileName ()).arg (line_counter)); } colours.append (QColor {r, g, b}); } } else { throw_qstring (QObject::tr ("Error opening waterfall palette file \"%1\".").arg (file.fileName ())); } return colours; } // GUI to design and manage waterfall palettes class Designer : public QDialog { Q_OBJECT; public: explicit Designer (Colours const& current, QWidget * parent = nullptr) : QDialog {parent} , colours_ {current} { ui_.setupUi (this); // context menu actions auto import_button = ui_.button_box->addButton ("&Import...", QDialogButtonBox::ActionRole); connect (import_button, &QPushButton::clicked, this, &Designer::import_palette); auto export_button = ui_.button_box->addButton ("&Export...", QDialogButtonBox::ActionRole); connect (export_button, &QPushButton::clicked, this, &Designer::export_palette); // load the table items ui_.colour_table_widget->setRowCount (colours_.size ()); for (int i {0}; i < colours_.size (); ++i) { insert_item (i); } // hookup the context menu handler connect (ui_.colour_table_widget, &QWidget::customContextMenuRequested, this, &Designer::context_menu); } Colours colours () const { return colours_; } // invoke the colour editor Q_SLOT void on_colour_table_widget_itemDoubleClicked (QTableWidgetItem * item) { auto new_colour = QColorDialog::getColor (item->background ().color (), this); if (new_colour.isValid ()) { item->setBackground (QBrush {new_colour}); colours_[item->row ()] = new_colour; } } private: void insert_item (int row) { std::unique_ptr item {new QTableWidgetItem {""}}; item->setBackground (QBrush {colours_[row]}); item->setFlags (Qt::ItemIsEnabled); ui_.colour_table_widget->setItem (row, 0, item.release ()); } void context_menu (QPoint const& p) { context_menu_.clear (); if (ui_.colour_table_widget->itemAt (p)) { auto delete_action = context_menu_.addAction (tr ("&Delete")); connect (delete_action, &QAction::triggered, [this] () { auto row = ui_.colour_table_widget->currentRow (); ui_.colour_table_widget->removeRow (row); colours_.removeAt (row); }); } auto insert_action = context_menu_.addAction (tr ("&Insert ...")); connect (insert_action, &QAction::triggered, [this] () { auto item = ui_.colour_table_widget->itemAt (menu_pos_); int row = item ? item->row () : colours_.size (); auto default_colour = QColor {0, 0, 0}; if (row > 0) { // use the prior row colour default_colour = colours_[row - 1]; } auto new_colour = QColorDialog::getColor (default_colour, this); if (new_colour.isValid ()) { ui_.colour_table_widget->insertRow (row); colours_.insert (row, new_colour); insert_item (row); } }); menu_pos_ = p; // save for context menu action handlers context_menu_.popup (ui_.colour_table_widget->mapToGlobal (p)); } void import_palette () { auto docs = QStandardPaths::writableLocation (QStandardPaths::DocumentsLocation); auto file_name = QFileDialog::getOpenFileName (this, tr ("Import Palette"), docs, tr ("Palettes (*.pal)")); if (!file_name.isEmpty ()) { colours_ = load_palette (file_name); } } void export_palette () { auto docs = QStandardPaths::writableLocation (QStandardPaths::DocumentsLocation); auto file_name = QFileDialog::getSaveFileName (this, tr ("Export Palette"), docs, tr ("Palettes (*.pal)")); if (!file_name.isEmpty ()) { if (!QFile::exists (file_name) && !file_name.contains ('.')) { file_name += ".pal"; } QFile file {file_name}; if (file.open (QFile::WriteOnly | QFile::Truncate | QFile::Text)) { QTextStream stream {&file}; Q_FOREACH (auto colour, colours_) { stream << colour.red () << ';' << colour.green () << ';' << colour.blue () << endl; } } else { throw_qstring (QObject::tr ("Error writing waterfall palette file \"%1\".").arg (file.fileName ())); } } } Ui::wf_palette_design_dialog ui_; Colours colours_; QMenu context_menu_; QPoint menu_pos_; }; } #include "WFPalette.moc" WFPalette::WFPalette (QString const& file_path) : colours_ {load_palette (file_path)} { } WFPalette::WFPalette (QList const& colour_list) : colours_ {colour_list} { } // generate an array of colours suitable for the waterfall plotter QVector WFPalette::interpolate () const { Colours colours {make_valid (colours_)}; QVector result; result.reserve (points); // do a linear gradient between each supplied colour point int interval = points / (colours.size () - 1); for (int i {0}; i < points; ++i) { int prior {i / interval}; int next {prior + 1}; if (next >= colours.size ()) { --next; --prior; } int increment {i - interval * prior}; int r {colours[prior].red () + int((increment * (colours[next].red () - colours[prior].red ()))/interval)}; int g {colours[prior].green () + int((increment * (colours[next].green () - colours[prior].green ()))/interval)}; int b {colours[prior].blue () + int((increment * (colours[next].blue () - colours[prior].blue ()))/interval)}; result.append (QColor {r, g, b}); } return result; } // invoke the palette designer bool WFPalette::design () { Designer designer {colours_}; if (QDialog::Accepted == designer.exec ()) { colours_ = designer.colours (); return true; } return false; }