mirror of
				https://github.com/saitohirga/WSJT-X.git
				synced 2025-11-03 21:40:52 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			329 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			329 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
#include "WFPalette.hpp"
 | 
						|
 | 
						|
#include <stdexcept>
 | 
						|
#include <memory>
 | 
						|
#include <algorithm>
 | 
						|
 | 
						|
#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>
 | 
						|
#include <QDebug>
 | 
						|
 | 
						|
#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)
 | 
						|
      {
 | 
						|
        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\": %2.").arg (file.fileName ()).arg (file.errorString ()));
 | 
						|
      }
 | 
						|
 | 
						|
    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);
 | 
						|
 | 
						|
      // hookup the context menu handler
 | 
						|
      connect (ui_.colour_table_widget, &QWidget::customContextMenuRequested, this, &Designer::context_menu);
 | 
						|
 | 
						|
      load_table ();
 | 
						|
    }
 | 
						|
 | 
						|
    void load_table ()
 | 
						|
    {
 | 
						|
      // load the table items
 | 
						|
      ui_.colour_table_widget->clear ();
 | 
						|
      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 ());
 | 
						|
    }
 | 
						|
 | 
						|
    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);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    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 ();
 | 
						|
                 insert_new_item (row, QColor {0, 0, 0});
 | 
						|
               });
 | 
						|
 | 
						|
      auto insert_after_action = context_menu_.addAction (tr ("Insert &after ..."));
 | 
						|
      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});
 | 
						|
               });
 | 
						|
 | 
						|
      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);
 | 
						|
          load_table ();
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    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 () <<
 | 
						|
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
 | 
						|
                 endl
 | 
						|
#else
 | 
						|
                 Qt::endl
 | 
						|
#endif
 | 
						|
                 ;
 | 
						|
                }
 | 
						|
            }
 | 
						|
          else
 | 
						|
            {
 | 
						|
              throw_qstring (QObject::tr ("Error writing waterfall palette file \"%1\": %2.").arg (file.fileName ()).arg (file.errorString ()));
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    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);
 | 
						|
 | 
						|
  // do a linear-ish gradient between each supplied colour point
 | 
						|
  auto interval = qreal (points) / (colours.size () - 1);
 | 
						|
 | 
						|
  for (int i {0}; i < points; ++i)
 | 
						|
    {
 | 
						|
      int prior = i / interval;
 | 
						|
 | 
						|
      if (prior >= (colours.size () - 1))
 | 
						|
        {
 | 
						|
          --prior;
 | 
						|
        }
 | 
						|
      auto next = prior + 1;
 | 
						|
      if (next >= colours.size ())
 | 
						|
        {
 | 
						|
          --next;
 | 
						|
        }
 | 
						|
 | 
						|
      // 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};
 | 
						|
      result.append (QColor::fromRgbF (r, g, b));
 | 
						|
 | 
						|
      // qDebug () << "Palette colour[" << (result.size () - 1) << "] =" << result[result.size () - 1] << "from: r:" << r << "g:" << g << "b:" << 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;
 | 
						|
}
 |