diff --git a/include/ui/hexedit.h b/include/ui/hexedit.h index 4845568e..57a24dde 100644 --- a/include/ui/hexedit.h +++ b/include/ui/hexedit.h @@ -24,6 +24,7 @@ #include #include #include +#include #include "ui/createchunkdialog.h" #include "ui/fileblobmodel.h" @@ -39,6 +40,12 @@ namespace ui { class HexEdit : public QAbstractScrollArea { Q_OBJECT public: + enum class UnprintablesMode { + // do not change the order as settings may invalidate. + Dots, + Windows1250, + }; + explicit HexEdit(FileBlobModel* dataModel, QItemSelectionModel* selectionModel = nullptr, QWidget* parent = nullptr); @@ -63,6 +70,8 @@ class HexEdit : public QAbstractScrollArea { in_insert_mode_ = in_insert_mode; } void saveToFile(const QString& file_name); + static QString unprintablesModeToString(UnprintablesMode mode); + void setUnprintablesMode(UnprintablesMode mode); public slots: void newBinData(); @@ -177,6 +186,9 @@ class HexEdit : public QAbstractScrollArea { QScopedPointer textEncoder_; util::EditEngine edit_engine_; + UnprintablesMode unprintables_mode_; + QTextCodec* windows1250_codec_; + void recalculateValues(); void initParseMenu(); void adjustBytesPerRowToWindowSize(); @@ -188,7 +200,10 @@ class HexEdit : public QAbstractScrollArea { WindowArea pointToWindowArea(QPoint pos); QString addressAsText(qint64 pos); QString hexRepresentationFromByte(uint64_t byte_val); - static QString asciiRepresentationFromByte(uint64_t byte_val); + + QString asciiRepresentationFromByte(uint64_t byte_val); + void updateAsciiCache(); + void updateHexCache(); static QColor byteTextColorFromByteValue(uint64_t byte_val); QColor byteBackroundColorFromPos(qint64 pos, bool modified); diff --git a/include/ui/hexeditwidget.h b/include/ui/hexeditwidget.h index fc7a1b08..82602420 100644 --- a/include/ui/hexeditwidget.h +++ b/include/ui/hexeditwidget.h @@ -92,6 +92,7 @@ class HexEditWidget : public View { void createActions(); void createToolBars(); void initParsersMenu(); + void initUnprintablesMenu(); void createSelectionInfo(); MainWindowWithDetachableDockWidgets* main_window_; @@ -127,6 +128,7 @@ class HexEditWidget : public View { QStringList parsers_ids_; QMenu parsers_menu_; + QMenu unprintables_menu_; QLabel* selection_label_; }; diff --git a/include/util/misc.h b/include/util/misc.h index 72455d62..60c84a26 100644 --- a/include/util/misc.h +++ b/include/util/misc.h @@ -26,6 +26,8 @@ inline size_t array_size(T (&/*arr*/)[SIZE]) { return SIZE; } +char ucharToChar(unsigned char value); + } // namespace misc } // namespace util } // namespace veles diff --git a/include/util/settings/hexedit.h b/include/util/settings/hexedit.h index 54f48c81..1c7181e0 100644 --- a/include/util/settings/hexedit.h +++ b/include/util/settings/hexedit.h @@ -16,6 +16,10 @@ */ #pragma once +#include + +#include "ui/hexedit.h" + namespace veles { namespace util { namespace settings { @@ -25,6 +29,8 @@ int columnsNumber(); void setColumnsNumber(int number); bool resizeColumnsToWindowWidth(); void setResizeColumnsToWindowWidth(bool on); +veles::ui::HexEdit::UnprintablesMode unprintablesMode(); +void setUnprintablesMode(veles::ui::HexEdit::UnprintablesMode mode); } // namespace hexedit } // namespace settings diff --git a/src/ui/hexedit.cc b/src/ui/hexedit.cc index a9e3a5ca..4b0ca3fb 100644 --- a/src/ui/hexedit.cc +++ b/src/ui/hexedit.cc @@ -29,6 +29,7 @@ #include "util/encoders/factory.h" #include "util/misc.h" #include "util/random.h" +#include "util/settings/hexedit.h" #include "util/settings/theme.h" using veles::util::misc::array_size; @@ -127,7 +128,9 @@ HexEdit::HexEdit(FileBlobModel* dataModel, QItemSelectionModel* selectionModel, cursor_pos_in_byte_(0), cursor_visible_(false), in_insert_mode_(false), - edit_engine_(dataModel_) { + edit_engine_(dataModel_), + windows1250_codec_(QTextCodec::codecForName("windows-1250")) { + // TODO(mkow) Log warning if codec is unavailable (== nullptr) auto font = util::settings::theme::font(); setFont(font); @@ -142,16 +145,9 @@ HexEdit::HexEdit(FileBlobModel* dataModel, QItemSelectionModel* selectionModel, recalculateValues(); // Initialize hex & ASCII text cache. - for (size_t i = 0; i < array_size(hex_text_cache_); i++) { - hex_text_cache_[i].setPerformanceHint(QStaticText::ModerateCaching); - hex_text_cache_[i].setText(hexRepresentationFromByte(i)); - hex_text_cache_[i].setTextFormat(Qt::PlainText); - } - for (size_t i = 0; i < array_size(ascii_text_cache_); i++) { - ascii_text_cache_[i].setPerformanceHint(QStaticText::ModerateCaching); - ascii_text_cache_[i].setText(asciiRepresentationFromByte(i)); - ascii_text_cache_[i].setTextFormat(Qt::PlainText); - } + unprintables_mode_ = util::settings::hexedit::unprintablesMode(); + updateAsciiCache(); + updateHexCache(); connect(verticalScrollBar(), &QAbstractSlider::valueChanged, this, &HexEdit::recalculateValues); @@ -602,6 +598,27 @@ void HexEdit::saveToFile(const QString& file_name) { saveDataToFile(0, edit_engine_.dataSize(), file_name); } +QString HexEdit::unprintablesModeToString(UnprintablesMode mode) { + switch (mode) { + case UnprintablesMode::Windows1250: + return "Windows-1250"; + case UnprintablesMode::Dots: + default: + return "Dots"; + } +} + +void HexEdit::setUnprintablesMode(UnprintablesMode mode) { + if (mode == UnprintablesMode::Windows1250 && windows1250_codec_ == nullptr) { + QMessageBox::warning(this, "Error", "Windows-1250 encoding is unavailable.", + QMessageBox::Ok); + return; + } + unprintables_mode_ = mode; + updateAsciiCache(); + viewport()->update(); +} + void HexEdit::discardChanges() { edit_engine_.clear(); recalculateValues(); @@ -643,10 +660,56 @@ QString HexEdit::addressAsText(qint64 pos) { } QString HexEdit::asciiRepresentationFromByte(uint64_t byte_val) { - if (byte_val >= 0x20 && byte_val < 0x7f) { - return QChar::fromLatin1(byte_val); + if (byte_val > 0xff) { + return "."; + } + if (windows1250_codec_ != nullptr && + unprintables_mode_ == UnprintablesMode::Windows1250) { + bool is_undefined_windows1250 = byte_val == 0x81 || byte_val == 0x83 || + byte_val == 0x88 || byte_val == 0x90 || + byte_val == 0x98; + + if (is_undefined_windows1250) { + return " "; + } + + char a = veles::util::misc::ucharToChar(byte_val); + QChar unicode_repr = windows1250_codec_->toUnicode(&a, 1).at(0); + + // 0x7f decodes to DEL in CP1250 which is unprintable + if (unicode_repr.isSpace() || unicode_repr.isNull() || byte_val == 0x7f) { + return " "; + } + + // printable ASCII chars + if (byte_val >= 0x20 && byte_val < 0x7f) { + return QChar::fromLatin1(byte_val); + } + + // unprintable ASCII chars + return byte_val > 0x7f ? windows1250_codec_->toUnicode(&a, 1) + : QChar(static_cast(byte_val) + + 0x180); // greek for < 0x20 + } + // dots mode + return (byte_val >= 0x20 && byte_val < 0x7f) ? QChar::fromLatin1(byte_val) + : QString("."); +} + +void HexEdit::updateAsciiCache() { + for (size_t i = 0; i < array_size(ascii_text_cache_); i++) { + ascii_text_cache_[i].setPerformanceHint(QStaticText::ModerateCaching); + ascii_text_cache_[i].setText(asciiRepresentationFromByte(i)); + ascii_text_cache_[i].setTextFormat(Qt::PlainText); + } +} + +void HexEdit::updateHexCache() { + for (size_t i = 0; i < array_size(hex_text_cache_); i++) { + hex_text_cache_[i].setPerformanceHint(QStaticText::ModerateCaching); + hex_text_cache_[i].setText(hexRepresentationFromByte(i)); + hex_text_cache_[i].setTextFormat(Qt::PlainText); } - return "."; } QColor HexEdit::byteTextColorFromByteValue(uint64_t byte_val) { diff --git a/src/ui/hexeditwidget.cc b/src/ui/hexeditwidget.cc index c6b1f001..ad281ef5 100644 --- a/src/ui/hexeditwidget.cc +++ b/src/ui/hexeditwidget.cc @@ -84,6 +84,9 @@ HexEditWidget::HexEditWidget( setParserIds(dynamic_cast( MainWindowWithDetachableDockWidgets::getFirstMainWindow()) ->parsersList()); + + initUnprintablesMenu(); + selectionChanged(0, 0); } @@ -282,6 +285,18 @@ void HexEditWidget::createToolBars() { addToolBar(edit_tool_bar_); view_tool_bar_ = new QToolBar(tr("View")); + + auto unprintables_tool_button = new QToolButton(); + unprintables_tool_button->setMenu(&unprintables_menu_); + unprintables_tool_button->setPopupMode(QToolButton::InstantPopup); + unprintables_tool_button->setIcon(QIcon(":/images/brightness.png")); + unprintables_tool_button->setText(tr("&Unprintables")); + unprintables_tool_button->setToolTip(tr("Appearance of unprintable bytes")); + unprintables_tool_button->setAutoRaise(true); + auto unprintables_widget_action = new QWidgetAction(view_tool_bar_); + unprintables_widget_action->setDefaultWidget(unprintables_tool_button); + + view_tool_bar_->addAction(unprintables_widget_action); view_tool_bar_->addAction(remove_column_act_); view_tool_bar_->addAction(add_column_act_); view_tool_bar_->addSeparator(); @@ -302,6 +317,21 @@ void HexEditWidget::initParsersMenu() { } } +void HexEditWidget::initUnprintablesMenu() { + unprintables_menu_.clear(); + + HexEdit::UnprintablesMode modes[] = {HexEdit::UnprintablesMode::Dots, + HexEdit::UnprintablesMode::Windows1250}; + + for (auto mode : modes) { + QAction* action = new QAction(hex_edit_->unprintablesModeToString(mode), + &unprintables_menu_); + connect(action, &QAction::triggered, + [this, mode]() { this->hex_edit_->setUnprintablesMode(mode); }); + unprintables_menu_.addAction(action); + } +} + void HexEditWidget::createSelectionInfo() { auto* widget_action = new QWidgetAction(this); auto* selection_panel = new QWidget; diff --git a/src/ui/optionsdialog.cc b/src/ui/optionsdialog.cc index 4a8f6b73..1c736334 100644 --- a/src/ui/optionsdialog.cc +++ b/src/ui/optionsdialog.cc @@ -14,10 +14,11 @@ * limitations under the License. * */ -#include "ui/optionsdialog.h" #include +#include "ui/hexedit.h" +#include "ui/optionsdialog.h" #include "ui_optionsdialog.h" #include "util/settings/hexedit.h" #include "util/settings/theme.h" @@ -48,6 +49,13 @@ void OptionsDialog::show() { ui->hexColumnsSpinBox->setValue(util::settings::hexedit::columnsNumber()); ui->hexColumnsSpinBox->setEnabled(checkState != Qt::Checked); + ui->unprintablesModeDots->setChecked( + util::settings::hexedit::unprintablesMode() == + veles::ui::HexEdit::UnprintablesMode::Dots); + ui->unprintablesModeWindows1250->setChecked( + util::settings::hexedit::unprintablesMode() == + veles::ui::HexEdit::UnprintablesMode::Windows1250); + QWidget::show(); } @@ -63,6 +71,15 @@ void OptionsDialog::accept() { ui->hexColumnsAutoCheckBox->checkState() == Qt::Checked); util::settings::hexedit::setColumnsNumber(ui->hexColumnsSpinBox->value()); + if (ui->unprintablesModeDots->isChecked()) { + util::settings::hexedit::setUnprintablesMode( + veles::ui::HexEdit::UnprintablesMode::Dots); + } + if (ui->unprintablesModeWindows1250->isChecked()) { + util::settings::hexedit::setUnprintablesMode( + veles::ui::HexEdit::UnprintablesMode::Windows1250); + } + if (restart_needed) { QMessageBox::about( this, tr("Options change"), diff --git a/src/ui/optionsdialog.ui b/src/ui/optionsdialog.ui index c4d38321..a7106986 100644 --- a/src/ui/optionsdialog.ui +++ b/src/ui/optionsdialog.ui @@ -6,8 +6,8 @@ 0 0 - 259 - 229 + 337 + 296 @@ -49,42 +49,73 @@ HexEdit defaults - - - + + + - + - Columns + Windows-1250 + + + + - - - - - 1 - - - 1024 - - - 16 - - - - - - - Resize to window width - - - - + + + 1 + + + 1024 + + + 16 + + + + + + + Resize to window width + + + + + + Columns + + + + + + + Qt::Horizontal + + + + + + + Dots + + + + + + + Unprintable characters + + + true + + + @@ -112,8 +143,8 @@ reject() - 290 - 160 + 299 + 286 286 @@ -128,8 +159,8 @@ accept() - 222 - 154 + 231 + 286 157 diff --git a/src/util/misc.cc b/src/util/misc.cc index b72795e2..fd4d3288 100644 --- a/src/util/misc.cc +++ b/src/util/misc.cc @@ -14,10 +14,29 @@ * limitations under the License. * */ + +#include + #include "util/misc.h" namespace veles { namespace util { -namespace misc {} // namespace misc +namespace misc { + +/** + * This magic performs the only safe method + * of casting uchar to char according to the C++ standard. +*/ +char ucharToChar(unsigned char value) { + if (value < 0x80) { + return static_cast(value); + } + + auto int_value = static_cast(value); + int_value -= 0x100; + return static_cast(int_value); +} + +} // namespace misc } // namespace util } // namespace veles diff --git a/src/util/settings/hexedit.cc b/src/util/settings/hexedit.cc index 2d7a95fc..36826074 100644 --- a/src/util/settings/hexedit.cc +++ b/src/util/settings/hexedit.cc @@ -43,6 +43,20 @@ void setResizeColumnsToWindowWidth(bool on) { settings.setValue("hexedit.resizeColumnsToWindowWidth", on); } +veles::ui::HexEdit::UnprintablesMode unprintablesMode() { + QSettings settings; + + auto default_value = + static_cast(veles::ui::HexEdit::UnprintablesMode::Dots); + return static_cast( + settings.value("hexedit.unprintablesMode", default_value).toInt()); +} + +void setUnprintablesMode(veles::ui::HexEdit::UnprintablesMode mode) { + QSettings settings; + settings.setValue("hexedit.unprintablesMode", static_cast(mode)); +} + } // namespace hexedit } // namespace settings } // namespace util