| 
									
										
										
										
											2014-03-31 00:03:44 +00:00
										 |  |  | #include "WFPalette.hpp"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <stdexcept>
 | 
					
						
							|  |  |  | #include <memory>
 | 
					
						
							| 
									
										
										
										
											2014-04-07 18:13:16 +00:00
										 |  |  | #include <algorithm>
 | 
					
						
							| 
									
										
										
										
											2014-03-31 00:03:44 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | #include <QMetaType>
 | 
					
						
							|  |  |  | #include <QObject>
 | 
					
						
							|  |  |  | #include <QFile>
 | 
					
						
							|  |  |  | #include <QTextStream>
 | 
					
						
							|  |  |  | #include <QString>
 | 
					
						
							|  |  |  | #include <QDialog>
 | 
					
						
							|  |  |  | #include <QTableWidget>
 | 
					
						
							|  |  |  | #include <QTableWidgetItem>
 | 
					
						
							|  |  |  | #include <QColorDialog>
 | 
					
						
							|  |  |  | #include <QColor>
 | 
					
						
							|  |  |  | #include <QBrush>
 | 
					
						
							|  |  |  | #include <QPoint>
 | 
					
						
							|  |  |  | #include <QMenu>
 | 
					
						
							|  |  |  | #include <QAction>
 | 
					
						
							|  |  |  | #include <QPushButton>
 | 
					
						
							|  |  |  | #include <QStandardPaths>
 | 
					
						
							|  |  |  | #include <QFileDialog>
 | 
					
						
							|  |  |  | #include <QFile>
 | 
					
						
							|  |  |  | #include <QTextStream>
 | 
					
						
							| 
									
										
										
										
											2014-04-07 18:13:16 +00:00
										 |  |  | #include <QDebug>
 | 
					
						
							| 
									
										
										
										
											2014-03-31 00:03:44 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | #include "qt_helpers.hpp"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "ui_wf_palette_design_dialog.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | namespace | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   int constexpr points {256}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   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) | 
					
						
							|  |  |  |       { | 
					
						
							| 
									
										
										
										
											2014-03-31 00:11:47 +00:00
										 |  |  |         throw_qstring (QObject::tr ("Too many colours in palette.")); | 
					
						
							| 
									
										
										
										
											2014-03-31 00:03:44 +00:00
										 |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     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.") | 
					
						
							| 
									
										
										
										
											2014-03-31 00:11:47 +00:00
										 |  |  |                                .arg (file.fileName ()).arg (line_counter)); | 
					
						
							| 
									
										
										
										
											2014-03-31 00:03:44 +00:00
										 |  |  |               } | 
					
						
							|  |  |  |             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 | 
					
						
							|  |  |  |       { | 
					
						
							| 
									
										
										
										
											2015-01-06 19:00:46 +00:00
										 |  |  |         throw_qstring (QObject::tr ("Error opening waterfall palette file \"%1\": %2.").arg (file.fileName ()).arg (file.errorString ())); | 
					
						
							| 
									
										
										
										
											2014-03-31 00:03:44 +00:00
										 |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     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
 | 
					
						
							| 
									
										
										
										
											2014-07-24 12:31:19 +00:00
										 |  |  |       auto import_button = ui_.button_box->addButton ("&Import...", QDialogButtonBox::ActionRole); | 
					
						
							| 
									
										
										
										
											2014-03-31 00:03:44 +00:00
										 |  |  |       connect (import_button, &QPushButton::clicked, this, &Designer::import_palette); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-07-24 12:31:19 +00:00
										 |  |  |       auto export_button = ui_.button_box->addButton ("&Export...", QDialogButtonBox::ActionRole); | 
					
						
							| 
									
										
										
										
											2014-03-31 00:03:44 +00:00
										 |  |  |       connect (export_button, &QPushButton::clicked, this, &Designer::export_palette); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-04-08 16:59:09 +00:00
										 |  |  |       // hookup the context menu handler
 | 
					
						
							|  |  |  |       connect (ui_.colour_table_widget, &QWidget::customContextMenuRequested, this, &Designer::context_menu); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       load_table (); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     void load_table () | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2014-03-31 00:03:44 +00:00
										 |  |  |       // load the table items
 | 
					
						
							| 
									
										
										
										
											2014-04-08 16:59:09 +00:00
										 |  |  |       ui_.colour_table_widget->clear (); | 
					
						
							| 
									
										
										
										
											2014-03-31 00:03:44 +00:00
										 |  |  |       ui_.colour_table_widget->setRowCount (colours_.size ()); | 
					
						
							|  |  |  |       for (int i {0}; i < colours_.size (); ++i) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |           insert_item (i); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     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<QTableWidgetItem> item {new QTableWidgetItem {""}}; | 
					
						
							|  |  |  |       item->setBackground (QBrush {colours_[row]}); | 
					
						
							|  |  |  |       item->setFlags (Qt::ItemIsEnabled); | 
					
						
							|  |  |  |       ui_.colour_table_widget->setItem (row, 0, item.release ()); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-04-07 17:32:54 +00:00
										 |  |  |     void insert_new_item (int row, QColor const& default_colour) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       // use the prior row colour as default if available
 | 
					
						
							|  |  |  |       auto new_colour = QColorDialog::getColor (row > 0 ? colours_[row - 1] : default_colour, this); | 
					
						
							|  |  |  |       if (new_colour.isValid ()) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |           ui_.colour_table_widget->insertRow (row); | 
					
						
							|  |  |  |           colours_.insert (row, new_colour); | 
					
						
							|  |  |  |           insert_item (row); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-03-31 00:03:44 +00:00
										 |  |  |     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); | 
					
						
							|  |  |  |                    }); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-07-24 12:31:19 +00:00
										 |  |  |       auto insert_action = context_menu_.addAction (tr ("&Insert ...")); | 
					
						
							| 
									
										
										
										
											2014-03-31 00:03:44 +00:00
										 |  |  |       connect (insert_action, &QAction::triggered, [this] () | 
					
						
							|  |  |  |                { | 
					
						
							|  |  |  |                  auto item = ui_.colour_table_widget->itemAt (menu_pos_); | 
					
						
							|  |  |  |                  int row = item ? item->row () : colours_.size (); | 
					
						
							| 
									
										
										
										
											2014-04-07 17:32:54 +00:00
										 |  |  |                  insert_new_item (row, QColor {0, 0, 0}); | 
					
						
							|  |  |  |                }); | 
					
						
							| 
									
										
										
										
											2014-03-31 00:03:44 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-07-24 12:31:19 +00:00
										 |  |  |       auto insert_after_action = context_menu_.addAction (tr ("Insert &after ...")); | 
					
						
							| 
									
										
										
										
											2014-04-07 17:32:54 +00:00
										 |  |  |       connect (insert_after_action, &QAction::triggered, [this] () | 
					
						
							|  |  |  |                { | 
					
						
							|  |  |  |                  auto item = ui_.colour_table_widget->itemAt (menu_pos_); | 
					
						
							|  |  |  |                  int row = item ? item->row () + 1 : colours_.size (); | 
					
						
							|  |  |  |                  insert_new_item( row, QColor {255, 255, 255}); | 
					
						
							| 
									
										
										
										
											2014-03-31 00:03:44 +00:00
										 |  |  |                }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       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); | 
					
						
							| 
									
										
										
										
											2014-04-08 16:59:09 +00:00
										 |  |  |           load_table (); | 
					
						
							| 
									
										
										
										
											2014-03-31 00:03:44 +00:00
										 |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     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_) | 
					
						
							|  |  |  |                 { | 
					
						
							| 
									
										
										
										
											2020-06-13 16:04:41 +01:00
										 |  |  |                   stream << colour.red () << ';' << colour.green () << ';' << colour.blue () << | 
					
						
							|  |  |  | #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
 | 
					
						
							|  |  |  |                  endl | 
					
						
							|  |  |  | #else
 | 
					
						
							|  |  |  |                  Qt::endl | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  |                  ; | 
					
						
							| 
									
										
										
										
											2014-03-31 00:03:44 +00:00
										 |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |           else | 
					
						
							|  |  |  |             { | 
					
						
							| 
									
										
										
										
											2015-01-06 19:00:46 +00:00
										 |  |  |               throw_qstring (QObject::tr ("Error writing waterfall palette file \"%1\": %2.").arg (file.fileName ()).arg (file.errorString ())); | 
					
						
							| 
									
										
										
										
											2014-03-31 00:03:44 +00:00
										 |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     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<QColor> const& colour_list) | 
					
						
							|  |  |  |   : colours_ {colour_list} | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // generate an array of colours suitable for the waterfall plotter
 | 
					
						
							|  |  |  | QVector<QColor> WFPalette::interpolate () const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   Colours colours {make_valid (colours_)}; | 
					
						
							|  |  |  |   QVector<QColor> result; | 
					
						
							|  |  |  |   result.reserve (points); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-04-08 16:59:09 +00:00
										 |  |  |   // do a linear-ish gradient between each supplied colour point
 | 
					
						
							|  |  |  |   auto interval = qreal (points) / (colours.size () - 1); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-03-31 00:03:44 +00:00
										 |  |  |   for (int i {0}; i < points; ++i) | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2014-04-08 16:59:09 +00:00
										 |  |  |       int prior = i / interval; | 
					
						
							| 
									
										
										
										
											2014-03-31 00:03:44 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-04-08 16:59:09 +00:00
										 |  |  |       if (prior >= (colours.size () - 1)) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |           --prior; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       auto next = prior + 1; | 
					
						
							| 
									
										
										
										
											2014-03-31 00:03:44 +00:00
										 |  |  |       if (next >= colours.size ()) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |           --next; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-04-08 16:59:09 +00:00
										 |  |  |       // qDebug () << "WFPalette::interpolate: prior:" << prior << "total:" << colours.size ();
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       auto increment = i - qreal (interval) * prior; | 
					
						
							|  |  |  |       qreal r {colours[prior].redF () + (increment * (colours[next].redF () - colours[prior].redF ()))/interval}; | 
					
						
							|  |  |  |       qreal g {colours[prior].greenF () + (increment * (colours[next].greenF () - colours[prior].greenF ()))/interval}; | 
					
						
							|  |  |  |       qreal b {colours[prior].blueF () + (increment * (colours[next].blueF () - colours[prior].blueF ()))/interval}; | 
					
						
							| 
									
										
										
										
											2014-04-07 18:13:16 +00:00
										 |  |  |       result.append (QColor::fromRgbF (r, g, b)); | 
					
						
							| 
									
										
										
										
											2014-04-08 16:59:09 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |       // qDebug () << "Palette colour[" << (result.size () - 1) << "] =" << result[result.size () - 1] << "from: r:" << r << "g:" << g << "b:" << b;
 | 
					
						
							| 
									
										
										
										
											2014-03-31 00:03:44 +00:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return result; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // invoke the palette designer
 | 
					
						
							|  |  |  | bool WFPalette::design () | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   Designer designer {colours_}; | 
					
						
							|  |  |  |   if (QDialog::Accepted == designer.exec ()) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       colours_ = designer.colours (); | 
					
						
							|  |  |  |       return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   return false; | 
					
						
							|  |  |  | } |