From 32e48a979ca78717a212800547c95ca0f1e67b8f Mon Sep 17 00:00:00 2001
From: Andrew Morgan <anoa@openmailbox.org>
Date: Thu, 7 Jul 2016 20:07:22 -0700
Subject: [PATCH] feat(textstyle): Change markdown syntax to be more intuitive

BREAKING CHANGE: Current markdown syntax is unintuitive, and thus we have agreed on switching to @SkyzohKey's implementation. Replaces all instances of Markdown with Text Styling. Closes #3404.
---
 doc/user_manual_en.md                       | 18 +++----
 src/chatlog/chatmessage.cpp                 | 60 +++++++++++----------
 src/chatlog/chatmessage.h                   |  2 +-
 src/persistence/settings.cpp                | 12 ++---
 src/persistence/settings.h                  |  8 +--
 src/widget/form/settings/generalform.cpp    |  8 +--
 src/widget/form/settings/generalform.h      |  2 +-
 src/widget/form/settings/generalsettings.ui | 17 +++---
 8 files changed, 68 insertions(+), 59 deletions(-)

diff --git a/doc/user_manual_en.md b/doc/user_manual_en.md
index 11303ddc3..e041b4502 100644
--- a/doc/user_manual_en.md
+++ b/doc/user_manual_en.md
@@ -220,15 +220,15 @@ Videochats and file transfers are currently unsupported in groupchats.
 ## Message Styling
 
 Similar to other messaging applications, qTox supports stylized text formatting.
-Formatting follows
-[Markdown syntax](https://daringfireball.net/projects/markdown/syntax), thus:
-
-* For **Bold**, surround text in double asterisks or underscores: `**text**`
-  or `__text__`
-* For **Italics**, surround text in single asterisks or underscores: `*text*`
-  or `_text_`
-* For **Strikethrough**, surround text in single tilde's: `~text~`
-* For **Underline**, surround text in single dashes: `-text-`
+
+* For **Bold**, surround text in single or double asterisks: `*text*`
+ or `**text**`
+* For **Italics**, surround text in single or double forward slashes: `/text/`
+ or `//text//`
+* For **Strikethrough**, surround text in single or double tilde's: `~text~`
+ or `~~text~~`
+* For **Underline**, surround text in single or double underscores: `_text_`
+ or `__text__`
 * For **Code**, surround your code in in single backticks: `` `text` ``
 
 Additionally, qTox supports three modes of Markdown parsing:
diff --git a/src/chatlog/chatmessage.cpp b/src/chatlog/chatmessage.cpp
index 864351a28..96be1123a 100644
--- a/src/chatlog/chatmessage.cpp
+++ b/src/chatlog/chatmessage.cpp
@@ -55,9 +55,9 @@ ChatMessage::Ptr ChatMessage::createChatMessage(const QString &sender, const QSt
     //quotes (green text)
     text = detectQuotes(detectAnchors(text), type);
 
-    //markdown
-    if (Settings::getInstance().getMarkdownPreference() != NONE)
-        text = detectMarkdown(text);
+    //text styling
+    if (Settings::getInstance().getStylePreference() != NONE)
+        text = detectStyle(text);
 
     switch(type)
     {
@@ -198,19 +198,21 @@ void ChatMessage::hideDate()
         c->hide();
 }
 
-QString ChatMessage::detectMarkdown(const QString &str)
+QString ChatMessage::detectStyle(const QString &str)
 {
     QString out = str;
 
-    // Create regex for certain markdown syntax
-    QRegExp exp("(\\*\\*)([^\\*\\*]{2,})(\\*\\*)"   // Bold    **text**
-                "|(\\*)([^\\*]{2,})(\\*)"           // Italics *text*
-                "|(\\_)([^\\_]{2,})(\\_)"           // Italics _text_
-                "|(\\_\\_)([^\\_\\_]{2,})(\\_\\_)"  // Bold    __text__
-                "|(\\-)([^\\-]{2,})(\\-)"           // Underline  -text-
-                "|(\\~)([^\\~]{2,})(\\~)"           // Strike  ~text~
-                "|(\\~~)([^\\~\\~]{2,})(\\~~)"      // Strike  ~~text~~
-                "|(\\`)([^\\`]{2,})(\\`)"           // Codeblock  `text`
+    // Create regex for text styling syntax
+    QRegExp exp("(\\*)([^\\*]{2,})(\\*)"            		// Bold         *text*
+                "|(\\*\\*)([^\\*\\*]{2,})(\\*\\*)"  		// Bold         **text**
+                "|(\\/)([^\\/]{2,})(\\/)"           		// Italics      /text/
+                "|(\\/\\/)([^\\/\\/]{2,})(\\/\\/)"  		// Italics      //text//
+                "|(\\_)([^\\_]{2,})(\\_)"           		// Underline    _text_
+                "|(\\_\\_)([^\\_\\_]{2,})(\\_\\_)"  		// Underline    __text__
+                "|(\\~)([^\\~]{2,})(\\~)"           		// Strike       ~text~
+                "|(\\~\\~)([^\\~\\~]{2,})(\\~\\~)"      	// Strike       ~~text~~
+                "|(\\`)([^\\`]{2,})(\\`)"                   // Codeblock    `text`
+                "|(\\`\\`\\`)([^\\`\\`\\`]{2,})(\\`\\`\\`)" // Codeblock    ```\ntext\n```
                 );
 
     int offset = 0;
@@ -224,28 +226,32 @@ QString ChatMessage::detectMarkdown(const QString &str)
         if ((snipCheck.startsWith(' ') || snipCheck.startsWith('>') || offset == 0)
             && ((snipCheck.endsWith(' ') || snipCheck.endsWith('<')) || offset + snippet.length() == out.length()))
         {
-            int mul = 0; // Determines how many characters to strip from markdown text
-            // Set mul depending on markdownPreference
-            if (Settings::getInstance().getMarkdownPreference() == WITHOUT_CHARS)
+            int mul = 0; // Determines how many characters to strip from text
+            // Set mul depending on styleownPreference
+            if (Settings::getInstance().getStylePreference() == WITHOUT_CHARS)
                 mul = 2;
 
-            // Match captured string to corresponding md format
-            if (exp.cap(1) == "**") // Bold **text**
+            // Match captured string to corresponding style format
+            if (exp.cap(1) == "*" && snippet.length() > 2) // Bold *text*
+                htmledSnippet = QString("<b>%1</b>").arg(snippet.mid(mul/2,snippet.length()-mul));
+            else if (exp.cap(4) == "**" && snippet.length() > 4) // Bold **text**
                 htmledSnippet = QString("<b>%1</b>").arg(snippet.mid(mul,snippet.length()-2*mul));
-            else if (exp.cap(4) == "*" && snippet.length() > 2) // Italics *text*
+            else if (exp.cap(7) == "/" && snippet.length() > 2) // Italics /text/
                 htmledSnippet = QString("<i>%1</i>").arg(snippet.mid(mul/2,snippet.length()-mul));
-            else if (exp.cap(7) == "_" && snippet.length() > 2) // Italics _text_
-                htmledSnippet = QString("<i>%1</i>").arg(snippet.mid(mul/2,snippet.length()-mul));
-            else if (exp.cap(10) == "__"&& snippet.length() > 4) // Bold __text__
-                htmledSnippet = QString("<b>%1</b>").arg(snippet.mid(mul,snippet.length()-2*mul));
-            else if (exp.cap(13) == "-" && snippet.length() > 2) // Underline -text-
+            else if (exp.cap(10) == "//" && snippet.length() > 4) // Italics //text//
+                htmledSnippet = QString("<i>%1</i>").arg(snippet.mid(mul,snippet.length()-2*mul));
+            else if (exp.cap(13) == "_"&& snippet.length() > 2) // Underline _text_
                 htmledSnippet = QString("<u>%1</u>").arg(snippet.mid(mul/2,snippet.length()-mul));
-            else if (exp.cap(16) == "~" && snippet.length() > 2) // Strikethrough ~text~
+            else if (exp.cap(16) == "__" && snippet.length() > 4) // Underline __text__
+                htmledSnippet = QString("<u>%1</u>").arg(snippet.mid(mul,snippet.length()-2*mul));
+            else if (exp.cap(19) == "~" && snippet.length() > 2) // Strike ~text~
                 htmledSnippet = QString("<s>%1</s>").arg(snippet.mid(mul/2,snippet.length()-mul));
-            else if (exp.cap(19) == "~~" && snippet.length() > 4) // Strikethrough ~~text~~
+            else if (exp.cap(22) == "~~" && snippet.length() > 4) // Strike ~~text~~
                 htmledSnippet = QString("<s>%1</s>").arg(snippet.mid(mul,snippet.length()-2*mul));
-            else if (exp.cap(22) == "`" && snippet.length() > 2) // Codeblock `text`
+            else if (exp.cap(25) == "`" && snippet.length() > 2) // Codeblock `text`
                 htmledSnippet = QString("<font color=#595959><code>%1</code></font>").arg(snippet.mid(mul/2,snippet.length()-mul));
+            else if (exp.cap(28) == "```" && snippet.length() > 6) // Codeblock ```text```
+                htmledSnippet = QString("<font color=#595959><code>%1</code></font>").arg(snippet.mid(4*mul,snippet.length()-8*mul));
             else
                 htmledSnippet = snippet;
             out.replace(offset, exp.cap().length(), htmledSnippet);
diff --git a/src/chatlog/chatmessage.h b/src/chatlog/chatmessage.h
index 2de1d668c..a7962cdd4 100644
--- a/src/chatlog/chatmessage.h
+++ b/src/chatlog/chatmessage.h
@@ -61,7 +61,7 @@ public:
     void hideDate();
 
 protected:
-    static QString detectMarkdown(const QString& str);
+    static QString detectStyle(const QString& str);
     static QString detectAnchors(const QString& str);
     static QString detectQuotes(const QString& str, MessageType type);
     static QString wrapDiv(const QString& str, const QString& div);
diff --git a/src/persistence/settings.cpp b/src/persistence/settings.cpp
index 50429c476..a7e2049b3 100644
--- a/src/persistence/settings.cpp
+++ b/src/persistence/settings.cpp
@@ -182,7 +182,7 @@ void Settings::loadGlobal()
         separateWindow = s.value("separateWindow", false).toBool();
         dontGroupWindows = s.value("dontGroupWindows", true).toBool();
         groupchatPosition = s.value("groupchatPosition", true).toBool();
-        markdownPreference = static_cast<MarkdownType>(s.value("markdownPreference", 1).toInt());
+        stylePreference = static_cast<StyleType>(s.value("stylePreference", 1).toInt());
     s.endGroup();
 
     s.beginGroup("Advanced");
@@ -434,7 +434,7 @@ void Settings::saveGlobal()
         s.setValue("groupchatPosition", groupchatPosition);
         s.setValue("autoSaveEnabled", autoSaveEnabled);
         s.setValue("globalAutoAcceptDir", globalAutoAcceptDir);
-        s.setValue("markdownPreference", static_cast<int>(markdownPreference));
+        s.setValue("stylePreference", static_cast<int>(stylePreference));
     s.endGroup();
 
     s.beginGroup("Advanced");
@@ -1249,16 +1249,16 @@ void Settings::setDateFormat(const QString &format)
     dateFormat = format;
 }
 
-MarkdownType Settings::getMarkdownPreference() const
+StyleType Settings::getStylePreference() const
 {
     QMutexLocker locker{&bigLock};
-    return markdownPreference;
+    return stylePreference;
 }
 
-void Settings::setMarkdownPreference(MarkdownType newValue)
+void Settings::setStylePreference(StyleType newValue)
 {
     QMutexLocker locker{&bigLock};
-    markdownPreference = newValue;
+    stylePreference = newValue;
 }
 
 QByteArray Settings::getWindowGeometry() const
diff --git a/src/persistence/settings.h b/src/persistence/settings.h
index f5168bd7d..69c9abe98 100644
--- a/src/persistence/settings.h
+++ b/src/persistence/settings.h
@@ -36,7 +36,7 @@ namespace Db { enum class syncType; }
 
 enum ProxyType {ptNone, ptSOCKS5, ptHTTP};
 
-enum MarkdownType {NONE, WITH_CHARS, WITHOUT_CHARS};
+enum StyleType {NONE, WITH_CHARS, WITHOUT_CHARS};
 
 class Settings : public QObject
 {
@@ -212,8 +212,8 @@ public:
     int getThemeColor() const;
     void setThemeColor(const int& value);
 
-    MarkdownType getMarkdownPreference() const;
-    void setMarkdownPreference(MarkdownType newValue);
+    StyleType getStylePreference() const;
+    void setStylePreference(StyleType newValue);
 
     bool isCurstomEmojiFont() const;
     void setCurstomEmojiFont(bool value);
@@ -426,7 +426,7 @@ private:
 
     // ChatView
     QFont chatMessageFont;
-    MarkdownType markdownPreference;
+    StyleType stylePreference;
     int firstColumnHandlePos;
     int secondColumnHandlePosFromRight;
     QString timestampFormat;
diff --git a/src/widget/form/settings/generalform.cpp b/src/widget/form/settings/generalform.cpp
index a5d2ba7fc..db512f81e 100644
--- a/src/widget/form/settings/generalform.cpp
+++ b/src/widget/form/settings/generalform.cpp
@@ -124,7 +124,7 @@ GeneralForm::GeneralForm(SettingsWidget *myParent) :
     const QFont chatBaseFont = s.getChatMessageFont();
     bodyUI->txtChatFontSize->setValue(QFontInfo(chatBaseFont).pixelSize());
     bodyUI->txtChatFont->setCurrentFont(chatBaseFont);
-    bodyUI->markdownComboBox->setCurrentIndex(s.getMarkdownPreference());
+    bodyUI->textStyleComboBox->setCurrentIndex(s.getStylePreference());
     bodyUI->cbAutorun->setChecked(s.getAutorun());
 
     bool showSystemTray = s.getShowSystemTray();
@@ -233,7 +233,7 @@ GeneralForm::GeneralForm(SettingsWidget *myParent) :
     connect(bodyUI->showInFront, &QCheckBox::stateChanged, this, &GeneralForm::onSetShowInFront);
     connect(bodyUI->notifySound, &QCheckBox::stateChanged, this, &GeneralForm::onSetNotifySound);
     connect(bodyUI->busySound, &QCheckBox::stateChanged, this, &GeneralForm::onSetBusySound);
-    connect(bodyUI->markdownComboBox, &QComboBox::currentTextChanged, this, &GeneralForm::onMarkdownUpdated);
+    connect(bodyUI->textStyleComboBox, &QComboBox::currentTextChanged, this, &GeneralForm::onStyleUpdated);
     connect(bodyUI->groupAlwaysNotify, &QCheckBox::stateChanged, this, &GeneralForm::onSetGroupAlwaysNotify);
     connect(bodyUI->autoacceptFiles, &QCheckBox::stateChanged, this, &GeneralForm::onAutoAcceptFileChange);
     connect(bodyUI->autoSaveFilesDir, SIGNAL(clicked()), this, SLOT(onAutoSaveDirChange()));
@@ -396,9 +396,9 @@ void GeneralForm::onUseEmoticonsChange()
     bodyUI->smileyPackBrowser->setEnabled(bodyUI->useEmoticons->isChecked());
 }
 
-void GeneralForm::onMarkdownUpdated()
+void GeneralForm::onStyleUpdated()
 {
-    Settings::getInstance().setMarkdownPreference(static_cast<MarkdownType>(bodyUI->markdownComboBox->currentIndex()));
+    Settings::getInstance().setStylePreference(static_cast<StyleType>(bodyUI->textStyleComboBox->currentIndex()));
 }
 
 void GeneralForm::onSetStatusChange()
diff --git a/src/widget/form/settings/generalform.h b/src/widget/form/settings/generalform.h
index d3188c6b8..6fad7acab 100644
--- a/src/widget/form/settings/generalform.h
+++ b/src/widget/form/settings/generalform.h
@@ -53,7 +53,7 @@ private slots:
     void onStyleSelected(QString style);
     void onTimestampSelected(int index);
     void onDateFormatSelected(int index);
-    void onMarkdownUpdated();
+    void onStyleUpdated();
     void onSetStatusChange();
     void onAutoAwayChanged();
     void onUseEmoticonsChange();
diff --git a/src/widget/form/settings/generalsettings.ui b/src/widget/form/settings/generalsettings.ui
index 8fefc7651..710a8e614 100644
--- a/src/widget/form/settings/generalsettings.ui
+++ b/src/widget/form/settings/generalsettings.ui
@@ -39,8 +39,8 @@
        <rect>
         <x>0</x>
         <y>0</y>
-        <width>1270</width>
-        <height>1468</height>
+        <width>1278</width>
+        <height>1382</height>
        </rect>
       </property>
       <layout class="QVBoxLayout" name="verticalLayout_4" stretch="0,0,0,1">
@@ -336,12 +336,12 @@ instead of closing itself.</string>
              </widget>
             </item>
             <item row="1" column="0">
-             <widget class="QLabel" name="transLabel_2">
+             <widget class="QLabel" name="textStyleLabel">
               <property name="toolTip">
-               <string>New Markdown preference may not load until qTox restarts.</string>
+               <string>New text styling preference may not load until qTox restarts.</string>
               </property>
               <property name="text">
-               <string>Markdown format:</string>
+               <string>Text Style format:</string>
               </property>
               <property name="alignment">
                <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
@@ -375,7 +375,7 @@ instead of closing itself.</string>
              </spacer>
             </item>
             <item row="1" column="1" colspan="2">
-             <widget class="QComboBox" name="markdownComboBox">
+             <widget class="QComboBox" name="textStyleComboBox">
               <property name="sizePolicy">
                <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
                 <horstretch>0</horstretch>
@@ -383,7 +383,10 @@ instead of closing itself.</string>
                </sizepolicy>
               </property>
               <property name="toolTip">
-               <string>Select Markdown preference.</string>
+               <string>Select text styling preference.</string>
+              </property>
+              <property name="currentIndex">
+               <number>1</number>
               </property>
               <item>
                <property name="text">