mirror of
https://github.com/saitohirga/WSJT-X.git
synced 2024-10-01 17:46:55 -04:00
85c4f6722b
The new class WFPalette encapsulates waterfall palettes including exporting and importing to disk files and interpolation for use in the waterfall plotter. A special entry in the palette list "User Defined" is now available along with a button to invoke a colour palette designer. The user defined palette definition is persistent across runs as it is saved in the application settings file. Palettes can now have any number of colours up to 256. The export function may be used to save user designed palettes; to be added to the built in resource palettes that are shipped with the application. git-svn-id: svn+ssh://svn.code.sf.net/p/wsjt/wsjt/branches/wsjtx@3964 ab8295b8-cf94-4d9e-aec4-7959e3be5d79
308 lines
8.9 KiB
C++
308 lines
8.9 KiB
C++
#include "WFPalette.hpp"
|
|
|
|
#include <stdexcept>
|
|
#include <memory>
|
|
|
|
#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 "qt_helpers.hpp"
|
|
|
|
#include "ui_wf_palette_design_dialog.h"
|
|
|
|
namespace
|
|
{
|
|
int constexpr points {256};
|
|
|
|
struct init
|
|
{
|
|
init ()
|
|
{
|
|
qRegisterMetaTypeStreamOperators<WFPalette::Colours> ("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 (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<QTableWidgetItem> 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<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 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;
|
|
}
|