git 使用writer_GitHub - Vpredictor/WriterFly: [QT/C++] 写作天下,为作家创造世界而生,执云作笔,诉尽平生意。...
寫作天下
簡介
為作家們創造世界而誕生,執云作筆,訴盡平生意。
集簡約UI與人性化AI于一體的碼字工具,無論是小說、作文、日記、報告,都能輕松駕馭。
QQ交流群:705849222
特點
已有功能:
自由的目錄:自動序號,導入導出
一體化界面:全局自定義主題
輸入動畫:獨創文字平滑輸入效果、光標平滑移動
智能引號:增刪移動全在一鍵之間
智能空格:縮進、標點、移動光標融為一體
智能回車:雙引號內回車、自動添加句末標點、自動縮進
自動標點:語氣詞后面自動加上標點符號
同音詞覆蓋:選詞錯誤,無需刪除,直接覆蓋舊內容
自動分段:換行/排版時,過長段落自動在合適的地方分段
自動提示:輸入時自動提示同義詞、相關詞、常用句
隨機取名:最輕便的取名方式,要啥來啥
一鍵排版:全自動
未來功能(近期繁忙,無力開發):
大綱列表
章節細綱
名片注釋
名字高亮
綜合搜索
全書替換
右下角通知卡片
自定義主題
自定義快捷鍵
小黑屋
云同步
排行榜
拼字房間
章節分享
“求評價”廣場
主角背包
快捷鍵
ctrl + ←/→ 按詞語移動
alt + ←/→ 按句子移動
ctrl + alt + ←/→ 按段落移動
ctrl + shift + ↑/↓ 擴大/縮小選擇
ctrl/alt + ↑/↓ 屏幕滾動,alt更快
空格鍵 智能空格
引號鍵 智能引號
回車鍵 智能回車
Tab鍵 句子補全+光標跳過
ctrl+T 一鍵排版
ctrl+D 句內同音詞替換(不必連續)
ctrl+F 章內文字搜索
ctrl+P 全局綜合搜索
更多快捷鍵請等待后期加入(或將支持自定義快捷鍵)
技術特點
開發環境:C++/Qt5.11.3,Qt Creator
運行環境:Windows、Android
1、標點AI
雖然是簡單暴力的枚舉(都是自己遇到的),但是有特判了上千種情況,也不容易吧……
若是人物語言描寫,還能根據人物的表情動作神態來分析情感程度,進一步提高準確度。
手動判斷,故覆蓋不全,不過在一定情況下,枚舉簡單易用,是為上上之選!
還有各種常用按鍵的自動化操作,極大程度上增加效率!
/**
* 小說語氣識別的AI類
*/
class NovelAIBase
{
/*...略了一些方法...*/
void operatorSmartSpace(); // 智能空格:按下空格觸發
void operatorSmartEnter(); // 智能回車:按下回車觸發
void operatorSmartQuotes(); // 智能引號:按下引號觸發
void operatorSmartBackspace(); // 智能刪除,按下刪除觸發
bool operatorAutoPunc(); // 自動標點:語氣詞自動觸發
bool operatorSentFinish(); // 句末標點:增加或轉化成結尾
void operatorSmartQuotes2(int left, int right); // 智能引號,僅在選中文本的情況下調用
virtual int getWordCount(QString str); // 字數統計,交給 NovelAI
QString getPunc(QString para/*段落*/, int pos/*光標*/); // ☆核心:取標點(句子)
QString getPunc(/*QString fullText, int pos*/); // 這個是全部文本中的某一部分
QString getPunc(int pos); // 全部文本,特定位置的標點
QString getPunc2(int pos); // 把","改成"。"
QString getPunc2(); // 把","改成"。"
int getDescTone(QString sent); // 句子語氣標點,影響語氣導向
QString getTalkTone(QString sent, QString sent2, int tone, QString left1, QString left2);
void updateCursorChars(); // 修改光標附近的字符
bool canDeletePairPunc(); // 是否能夠刪除成對文本(能刪就刪)
bool isCursorInQuote(QString text, int pos); // 是否在引號里面
virtual void moveCursor(int x) = 0; // 移動光標
virtual void insertText(int pos, QString text) = 0; // 插入文本
virtual void insertText(QString text) = 0; // 插入文本
virtual void deleteText(int start, int end) = 0; // 刪除文本
// bool isNextLang(); // 是否是后一句話(雙引號前面多的是逗號)
bool isSentPunc(QString str); // 是否為句末標點(不包含引號和特殊字符,不包括逗號)
bool isSentSplitPunc(QString str); // 是否為句子分割標點(包含逗號)
bool isSentSplit(QString str); // 是否為句子分割符(各類標點,包括逗號)
bool isASCIIPunc(QString str); // 是否為英文標點(不包含引號和特殊字符)
bool isBlankChar(QString str); // 是否為空白符
bool isBlankChar2(QString str); // 是否為換行之外的空白符
bool isBlankString(QString str); // 是否為空白字符串
bool isSymPairLeft(QString str); // 是否為對稱標點左邊的
bool isSymPairRight(QString str); // 是否為對稱標點右邊的
QString getSymPairLeftByRight(QString str); // 根據右邊括號獲取左邊的括號
QString getSymPairRightByLeft(QString str); // 根據右邊括號獲取左邊的括號
QString getCursorFrontSent(); // 獲取當前面的句子
QString getCurrentChar(int x); // 獲取當前位置的附近漢字
bool isQuoteColon(QString str); // 漢字后面是否需要加標點
protected:
QString _text, _pre_text; // 文本
int _pos, _pre_pos, _dif; // 光標位置和字數差
QString _left1, _left2, _left3, _right1, _right2; // 光標附近的文本
bool isInQuotes; // 是否在引號里面(用來判斷是否為語言或者描述)
private:
QString _shuo_blacklists, _dao_whitelists, _wen_blacklists; // “說”黑名單、“道”白名單、“問”黑名單
QString _symbol_pair_lefts, _symbol_pair_rights; // 成對符號左邊/右邊
QString _quote_no_colon, _quantifiers; // 引號前面沒有冒號
QString _sent_puncs, _sent_split, _sent_split_puncs, _blank_chars; // 句末標點、句子分割符、空白符
QString _auto_punc_whitelists; // 自動標點白名單
};
子類為NovelAI,再下一個子類 NovelEditor 使用到了多繼承,同時繼承 QTextEdit 與 NovelAI。
其實設計得不是很好,AI 類需要大量調用 Edit 的方法,于是加了許多虛函數,將三個類緊緊耦合到一起(不過關系不大,本來就是怕一個文件代碼太長而分開的)。
2、仿IDE思路
當今所謂的寫作軟件,單論寫作方面,其實和一個記事本沒多少差別,無非多了目錄與自動縮進,以及簡單的自動保存等。其一些亮點,比如強制寫作的小黑屋、多人競爭的在線拼字,都無法從“碼字”本身來幫助作者達到更高效的辦公,這些附加的功能甚至會讓作者分心,無法好好靜心創作。
寫作天下定位一款單純的“文學創作”編輯器,主打從“寫作”本身提高效率,弱化與文字無關的功能。或許后期將會添加小黑屋、拼字等,但絕不會將這些作為主要功能。
備注:近期繁忙,無力開發,所以只是“思路”!
一些功能:
增強的編輯功能
自動提示
面向對象寫作:名片系統
文字高亮(名片高亮)
高度個性化設置(功能細節、快捷鍵、主題等)
追求自動的編輯器
一句話:能自動的,絕不手動!
為了實現更加人性化的功能,程序中使用了大量的算法。
比如括號匹配功能,輸入左括號時自動添加右括號、刪除鍵刪除成對的符號,這里使用數據結構中的棧,判斷光標前后各自左右括號的數量,入棧出棧,來判斷是否需要添加/刪除,而不僅僅依靠光標前一個字符。
還有光標移動、在標點前面換行、在語言描寫內回車自動插入前后引號、修改引號前面的逗號與冒號等,一個按鍵,多種功能。
輕松提示
經過多種方案的性能測試,選取了速度最快、運行最穩定的方式。
以下為輸入文字后自動提示算法:
/**
* 某個句子的某個位置處進行搜索
* @param sent 欲搜索的完整句子(短句,不包含標點)
* @param cursor 光標在句子中的相對位置
* @return 是否找到
*/
bool Lexicons::surroundSearch(QString sent, int cursor)
{
int len = sent.length();
search_result.clear();
match_sentence = false;
bool find = false;
matched_key = "";
int start_pos = 0;
QString l1 = "", l2 = "", l4 = "";
if (cursor >= 1)
l1 = sent.mid(cursor-1, 1);
if (cursor >= 2)
l2 = sent.mid(cursor-2, 2);
if (cursor >= 4)
l4 = sent.mid(cursor-4, 4);
if (random_inited)
{
// 隨機種類列表
if (((matched_key = l2) == "隨機")
|| ((matched_key = l2) == "取名")
|| ((matched_key = l4) == "隨機取名"))
if (searchRandom("隨機取名"))
{
matched_case = COMPLETER_CONTENT_RAND_LIST;
return true;
}
// 姓氏
if (surname_inited && (((matched_key = l1) == "姓")
|| ((matched_key = l2) == "姓氏")))
if (searchRandom("姓氏"))
{
matched_case = COMPLETER_CONTENT_SURN;
return true;
}
// 人名
if (name_inited && (((matched_key = l2) == "人名")
|| ((matched_key = l2) == "名字")))
if (searchRandom("人名"))
{
matched_case = COMPLETER_CONTENT_NAME;
return true;
}
// 隨機列表
for (QString s : random_sort_list)
{
if (cursor >= s.length() && sent.mid(cursor-s.length(), s.length()) == s && isFileExist(lexicon_dir + "random/" + s + ".txt"))
{
matched_key = s;
matched_case = COMPLETER_CONTENT_RAND;
searchRandom(s);
return true;
}
}
}
// 搜索4個字
if (!find)
{
start_pos = 0; // 開始搜索的位置
if (start_pos < cursor-4) start_pos = cursor-4;
for (int i = start_pos; i <= len-4 && i < cursor; i++)
{
if (search(sent.mid(i, 4), true))
{
find = true;
matched_key = sent.mid(i, 4);
break;
}
}
}
// 搜索三個字
if (!find)
{
start_pos = 0;
if (start_pos < cursor-3) start_pos = cursor-3;
for (int i = start_pos; i <= len-3 && i < cursor; i++)
if (search(sent.mid(i, 3), true))
{
find = true;
matched_key = sent.mid(i, 3);
break;
}
}
// 搜索兩個字
if (!find)
{
start_pos = 0;
if (start_pos < cursor-2) start_pos = cursor-2;
for (int i = start_pos; i <= len-2 && i < cursor; i++)
if (search(sent.mid(i, 2), true))
{
find = true;
matched_key = sent.mid(i, 2);
break;
}
}
//qDebug() << "surround search:" << matched_key << " result:" << search_result;
search_last = sent;
matched_case = COMPLETER_CONTENT_WORD;
return find;
}
/**
* 在詞庫中搜索某一個詞語
* @param key 欲搜索的詞語
* @param add 是否為添加模式。如果不是,則先清空已經找到的列表
* @return 是否有搜索結果
*/
bool Lexicons::search(QString key, bool add)
{
if (key.isEmpty()) return false;
/* // 上次搜索的緩沖區,但是沒必要了,因為每次surroundSearch的時候
* // 都把上次的搜索結果清空了,key還在但是result沒了
if (key == search_last)
{
return true;
}*/
if (!add) search_result.clear();
bool find = false;
int key_len = key.length();
if (synonym_inited)
{
QStringList synonym_list;
int pos = 0;
while (1)
{
//pos = synonym_text.indexOf(key, pos);
pos = synonym_text.indexOf(QRegExp("\\b"+key+"\\b"), pos);
if (pos == -1) break;
int left = synonym_text.lastIndexOf("\n", pos)+1;
int right = synonym_text.indexOf("\n", pos);
if (right == -1) right = synonym_text.length();
QString para = synonym_text.mid(left, right-left);
QStringList list = para.split(" ", QString::SkipEmptyParts);
// TODO 后期將改成 QList
synonym_list.append(list);
pos += key_len;
find = true;
}
if (synonym_list.size() > 0 && shouldRandom())
{
if (shouldRandom())
{
std::random_shuffle(synonym_list.begin(), synonym_list.end());
}
search_result.append(synonym_list);
}
}
if (related_inited)
{
QStringList related_list;
int pos = 0;
while (1)
{
pos = related_text.indexOf(QRegExp("\\b"+key+"\\b"), pos);
if (pos == -1) break; // 找不到了
if (pos > 0 && related_text.mid(pos-1, 1) == "{") // 是標題
{
int left = related_text.indexOf("[", pos)+1;
int right = related_text.indexOf("]", pos);
if (right < left-1) // 出現了錯誤
{
pos = left+1;
continue;
}
if (right == -1) right = related_text.length();
QString para = related_text.mid(left, right-left);
QStringList list = para.split(" ", QString::SkipEmptyParts);
// TODO 后期將改成 QList
related_list.append(list);
pos = right;
}
else // 是內容
{
// 如果是本程序標準格式
int left = related_text.lastIndexOf("[", pos)+1;
int right = related_text.indexOf("]", pos);
// 如果只是一段一段分開的
int left_n = related_text.lastIndexOf("\n", pos)+1;
if (left_n >= left)
{
left = left_n;
right = related_text.indexOf("\n", pos);
}
if (right == -1) right = related_text.length();
QString para = related_text.mid(left, right-left);
QStringList list = para.split(" ", QString::SkipEmptyParts);
// TODO 后期將改成 QList
related_list.append(list);
pos = right;
}
find = true;
}
if (related_list.size() > 0)
{
if (shouldRandom())
{
std::random_shuffle(related_list.begin(), related_list.end());
}
search_result.append(related_list);
}
}
if (sentence_inited && find)
{
if (sentence_text.indexOf(key) > -1)
{
match_sentence = true;
search_result.append("-->");
}
}
//qDebug() << "search:" << key << " result:" << search_result;
return find;
}
/**
* 通過一個詞語,來獲取應該顯示的隨機取名提示列表
* @param key 欲搜索的詞語
* @return 是否找到結果
*/
bool Lexicons::searchRandom(QString key)
{
// ==== 隨機取名列表 ====
if (key == "隨機取名" || key == "隨機" || key == "取名")
{
for (QString s : random_sort_list)
search_result.append(s);
std::random_shuffle(search_result.begin(), search_result.end());
return true;
}
// ==== 隨機取名具體 ====
for (int i = 0; i < random_sort_list.size(); i++)
if (random_sort_list.at(i) == key)
{
QStringList list = random_text_list.at(i).split(" ", QString::SkipEmptyParts);
search_result = list;
std::random_shuffle(search_result.begin(), search_result.end());
search_result = search_result.mid(0, 100);
return true;
}
return false;
}
/**
* 搜索后返回結果
* @return 搜索結果
*/
QStringList Lexicons::getResult()
{
return search_result;
}
3、平滑輸入
創作,或是為了寧靜的內心,或是為了美好的生活,終是需要解放內心的壓抑。
而寫作天下的輸入動畫與平滑光標,為了打造愉悅的創作環境,可謂是嘔心瀝血。
動畫算法
輸入動畫
輸入動畫中每一個字符都是一個Label對象,設置文字顏色為對應字符,然后使用一個動畫管理器進行統一管理,開放管理器的API給編輯器,編輯文字的同時通過管理器調整所有文字的動畫,便不會出現沖突的情況。
難點在要將每個Label和對應文字連在一起,尤其是支持實時修改。看了其他相同功能的開源項目,大多只支持英文,并且是監聽到文字輸入后才開始動畫,等動畫結束再上屏,無法在輸入后立即刪除或者修改,非常影響效率。
/**
* 輸入動畫管理器類
*/
class EditorInputManager : public QObject
{
Q_OBJECT
public:
EditorInputManager();
void setEditor(QTextEdit* edit); // 設置開啟動畫對應的編輯器
void setColor(QColor color); // 設置全文顏色
void textChanged(int old_position, int diff); // 文字改變時調整動畫控件
void updateRect(int range_start, int rande_end); // 更新動畫控件的位置
void addInputAnimation(QPoint point, QString str, int position, int delay, int duration); // 添加一個文字動畫
void addInputAnimation(QPoint point, QString str, int position, int delay, int duration, QColor color); // 添加一個帶有不同顏色的文字動畫
void updateTextColor(int current_position); // 修改全文顏色,同時修改正在動畫的顏色
public slots:
void aniFinished(int position, EditorInputCharactor *); // 動畫結束,傳參文字位置
private:
QTextEdit* _edit; // 編輯器
QColor font_color; // 全文顏色
QList ani_list; // 動畫控件列表
};
/**
* 輸入動畫的某一個字符實體,存儲動畫信息、光標位置、光標坐標、光標文字等
*/
class EditorInputCharactor : public QLabel
{
Q_OBJECT
Q_PROPERTY(int fontsize READ getFontSize WRITE setFontSize RESET resetFontSize)
public:
EditorInputCharactor(QWidget* parent, QPoint point, QString str, int position, QFont font, int delay = 0, int duration = 200);
int getPosition(); // 獲取動畫的文字位置,更新位置、動畫結束文字變回原色時要用到
void changePosition(int x); // 修改動畫的位置
void updateRect(QPoint point); // 更新控件坐標
// 字體動畫接口
void setFontSize(int x);
int getFontSize();
void resetFontSize();
signals:
void aniFinished(int position, EditorInputCharactor* charactor); // 動畫結束
private:
QPoint point; // 坐標
QString str;
int position; // 文字位置
int duration; // 動畫時長,同時輸入的不同位置的文字時長不一樣
QPropertyAnimation *ani; // 動畫對象
int origin_position; // 一開始的位置
int font_size; // 文字大小動畫屬性
int font_size_l;
};
平滑光標
平滑光標添加一個矩形控件為光標樣式,每次改變時存儲其坐標,然后借助動畫移動控件。
其實還簡單,但難點在要好好控制什么時候才開始動畫,不然光標會亂飄或者閃動,看起來效果很差。
最大的難點在于,與功能“光標行固定”以及“底部光標固定”有很大的沖突,還有每次修改文字時(這是自定義事件經常使用QTextEdit.setText()方法才會出現的問題)需要大大約束光標動畫的時機。
4、MVD目錄結構
Qt標準 QListView 的MVD模型,一開始以為很難,剛走來發現其實就是重寫幾個方法。
這里使用的 MVD似乎與標準的不太一樣,看下去就知道了。
Model
由于寫作天下的目錄比較復雜,所以專門寫了一個類NovelDirMData來存放多個NovelDirItem的信息。
此處 Model 用到了多繼承,以及友元。(其實這個模型已經變了味了)
class NovelDirModel : public QAbstractListModel, public NovelDirMData
{
/*...其余代碼略...*/
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const
{
// 不需要 index 的 data
if (role == Qt::UserRole+DRole_ROLL_COUNT)
{
return roll_subs.size();
}
else if (role == Qt::UserRole+DRole_CR_COUNT)
{
return cr_list.size();
}
else if (role == Qt::UserRole+DRole_ROLL_NAMES)
{
return roll_names;
}
// 判斷 index
if ((!index.isValid()) || (index.row() >= cr_list.size()))
return QVariant();
NovelDirItem item = cr_list.at(index.row());
if (role == Qt::UserRole+DRole_CHP_NAME)
{
return item.getName(); // 章節名
}
else if (role == Qt::UserRole+DRole_CHP_NUM)
{
return item.getNumber(); // 序號
}
else if (role == Qt::UserRole+DRole_CHP_NUM_CN)
{
return item.getNumber_cn(); // 序號
}
else if (role == Qt::UserRole+DRole_CHP_ROLLINDEX)
{
return item.getRollIndex(); // 序號
}
else if (role == Qt::UserRole+DRole_CHP_CHPTINDEX)
{
return item.getChptIndex(); // 序號
}
else if (role == Qt::UserRole+DRole_CHP_DETAIL)
{
return item.getDetail(); // 細綱
}
else if (role == Qt::UserRole+DRole_CHP_OPENING)
{
return item.isOpening(); // 是否編輯中
}
else if (role == Qt::UserRole+DRole_CHP_ISROLL)
{
return item.isRoll(); // 是否為分卷
}
else if (role == Qt::UserRole+DRole_CHP_ISHIDE)
{
return item.isHide(); // 是否隱藏
}
else if (role == Qt::UserRole+DRole_CHP_STEMP)
{
return item.getS_temp(); // 雙擊編輯,出錯后恢復原來的文本
}
else if (role == Qt::UserRole+DRole_ROLL_SUBS)
{
int rIndex = item.getRollIndex();
return roll_subs[rIndex]; // 分卷章數量
}
else if (role == Qt::UserRole+DRole_ROLL_COUNT)
{
return roll_subs.size();
}
else if (role == Qt::UserRole+DRole_CR_COUNT)
{
return cr_list.size();
}
else if (role == Qt::UserRole+DRole_CHP_FULLNAME)
{
return item.getFullChapterName();
}
else if (role == Qt::UserRole+DRole_RC_ANIMATING)
{
return item.isAnimating();
}
else if (role == Qt::UserRole+DRole_ROLL_NAMES)
{
return roll_names;
}
else if (role == Qt::UserRole+DRole_RC_SELECTING)
{
return item.isSelecting();
}
return QVariant();
}
// 重寫 flags 和 setData 使 Model 可雙擊編輯
Qt::ItemFlags flags(const QModelIndex &index) const
{
Qt::ItemFlags flags = QAbstractItemModel::flags(index);
//if (index.row() > 0 && us->one_click) // 作品相關卷名不允許更改
//flags |= Qt::ItemIsEditable;
if (index.row() > 0)
flags |= Qt::ItemIsEditable;
return flags;
}
bool setData(const QModelIndex &index, const QVariant &value, int role)
{
if (!index.isValid()) return false;
NovelDirItem item = cr_list.at(index.row());
if (role == Qt::EditRole) // 修改章節名
{
QString old_name = item.getName();
bool isNew = false;
if (canRegExp(old_name, "新[章卷]\\d+")) // 分卷是聚集焦點用
isNew = true;
bool rst = tryChangeName(index.row(), value.toString());
if (/*isNew && */rst) // 重命名成功
{
if (isNew) // 如果是新建的
emit signalOpenChapter(index.row());
else // 否則就是重命名,修改已經打開的舊名字標簽頁
emit signalChangeFullName(novel_name, value.toString());
}
return rst;
}
else if (role == Qt::UserRole+DRole_CHP_STEMP)
{
cr_list[index.row()].setS_temp(value.toString());
}
else if (role == Qt::UserRole+DRole_CHP_FULLNAME)
{
cr_list[index.row()].setFullChapterName(value.toString());
deb(value.toString(), "model.setFullChapterName");
}
return true;
}
}
View
這里重寫的方法有點多……就放一個設置Model和Delegate的。
不是很標準,因為為了方便,把應該解耦的Model和Delegate都聲明為成員變量了。
// 設置 delegate
novel_dir_delegate = new NovelDirDelegate(this);
setItemDelegate(novel_dir_delegate);
// 設置 model
novel_dir_model = new NovelDirModel(this);
setModel(novel_dir_model);
Delegate
因為用到了作品設置,有用戶自定義的完全不同的目錄結構,所以和Model一樣使用了多繼承。
有三大特色:
用戶自定義文字顯示:例如,阿拉伯數字自動排序、不顯示分卷等
圓形顯示效果:不知道該怎么描述……至少要比默認的藍色選中效果好看吧?
支持全局主題色/點綴色,實時修改
/**
* 目錄列表代理類
*/
class NovelDirDelegate : public QItemDelegate, public NovelDirSettings
{
/* ... 略了一些 ... */
QString getItemText(const QModelIndex &index) const
{ /*...獲取章節序號,略...*/}
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QStyleOptionViewItem op(option);
op.palette.setColor(QPalette::Highlight, us->getOpacityColor(us->accent_color, 128));
painter->save();
// 獲取文字
QString text= getItemText(index);
int deviate = DIR_DEVIATE; // 初始偏移位置
int subs = -1; // 分卷數量,章節為-1
if (index.data(Qt::UserRole+DRole_CHP_ISROLL).toBool()) // 是分卷,畫數字
{
subs = index.data(Qt::UserRole+DRole_ROLL_SUBS).toInt();
}
else // 是章節
{
if (isNoRoll() && index.data(Qt::UserRole+DRole_CHP_NUM).toInt() > 0) // 不顯示分卷 且 是正文,則取消縮進
;
else
deviate += DIR_DEVIATE_ADD; // 增加章節的縮進
}
// 獲取文字區域并繪制
QFontMetrics fm(painter->font());
QRect text_rect = QRect(op.rect.topLeft()+QPoint(deviate,(op.decorationSize.height()+us->mainwin_sidebar_spacing*2-fm.height())/2),QSize(fm.width(text),fm.height()));
if (!us->round_view)
drawBackground(painter, op, index); // 畫選中的顏色的(不加這行的話就是透明選中)
// 先繪制背景
if (!index.data(Qt::UserRole+DRole_RC_ANIMATING).toBool()) // 不是在動畫中(動畫時不顯示文字,即背景透明)
{
// 繪制選中狀態的紙片形狀背景
if (us->round_view && (option.state & QStyle::State_Selected))
{
int text_padding = text_rect.height()/2-1;
QColor bg_color = us->getOpacityColor(us->accent_color, 128);
QRect round_rect(text_rect.left()-text_padding, text_rect.top()-3, text_rect.width()+text_padding*2, text_rect.height()+6);
painter->setRenderHint(QPainter::Antialiasing);
QPen pen(bg_color, Qt::NoPen);
painter->setPen(pen);
QPainterPath path;
int radius = round_rect.height()/2-1;
path.addRoundedRect(round_rect, radius, radius);
painter->fillPath(path, bg_color);
painter->drawPath(path);
painter->setRenderHint(QPainter::Antialiasing, false);
}
// 繪制文字
if (isNoRoll() && subs >= 0) // 不使用分卷,并且剛好是分卷,則使用灰色,弱化分卷存在感
{
painter->setPen(QColor(128, 128, 128));
}
else
painter->setPen(QColor(0, 0, 0));
painter->drawText(text_rect, text);
// 畫左邊的小標記
if (subs >= 0) // 是分卷(章節默認 -1)
{
}
else // 是章節,繪制打開狀態
{
if (index.data(Qt::UserRole+DRole_CHP_OPENING).toBool()) // 打開狀態中,畫右邊的小點
{
QRect state_rect = QRect(op.rect.topLeft()+QPoint(0,1),QSize(1,op.rect.height()-2));
painter->setPen(QPen(QColor(us->accent_color), Qt::SolidLine));
painter->setBrush(QBrush(QColor(us->accent_color), Qt::SolidPattern));
painter->drawRect(state_rect);
}
}
// 畫右邊的小標記,分卷數量
if (subs >= 0) // 是分卷(章節默認 -1)
{
if (subs == 0) // 沒有章節,灰色的數字
{
painter->setPen(QColor(128, 128, 128));
}
else // 章節,黑偏灰色的
{
painter->setPen(QColor(64, 64, 64));
}
QString subs_text = QString("%1").arg(subs);
QRect display_rect2 = QRect(op.rect.topRight()-QPoint(fm.width(subs_text)+10,-3),QSize(fm.width(subs_text),fm.height()));
painter->drawText(display_rect2, subs_text);
}
}
// drawFocus(painter, option, displayRect); // 畫選區虛線
drawFocus(painter, op, QRect(0, 0, 0, 0));
painter->restore();
}
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
Q_UNUSED(index);
//QSize size = option.rect.size(); // 這個是整個QListView的矩形……
QSize size = option.decorationSize;
size.setHeight(size.height()+us->mainwin_sidebar_spacing*2);
return size;//QItemDelegate::sizeHint(o2, index);
}
/* 編輯框修改名字 */
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex&index) const
{
Q_UNUSED(option);
Q_UNUSED(index);
QLineEdit* edit = new QLineEdit(parent);
edit->setAcceptDrops(false);
QString style = "";//"background-color:"+us->getColorString(us->accent_color)+";";
if (us->round_view)
{
QString text= getItemText(index);
QFontMetrics fm(edit->font());
int r = fm.height()/2;
QString rs = QString("%1").arg(r);
style += "border:1px;border-radius:"+rs+"px; padding-left: "+rs+"px;";
}
else
style += "border:none;";
edit->setStyleSheet(style);
QPalette palette = edit->palette();
if (us->editor_font_color.alpha() > 0)
palette.setColor(QPalette::Text, us->editor_font_color);
palette.setColor(QPalette::Base, us->accent_color/*editor_bg_color*/);
if (us->editor_font_selection.alpha() > 0)
palette.setColor(QPalette::HighlightedText, us->editor_font_selection);
palette.setColor(QPalette::Highlight, us->editor_bg_selection);
edit->setPalette(palette);
return edit;
}
void setEditorData(QWidget *editor, const QModelIndex &index) const
{
QString name = index.data(Qt::UserRole+DRole_CHP_NAME).toString();
if (index.data(Qt::UserRole+DRole_CHP_STEMP).toString() != "")
name = index.data(Qt::UserRole+DRole_CHP_STEMP).toString();
QLineEdit* edit = static_cast(editor);
// renameEdit = edit;
edit->setText(name);
}
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
QLineEdit* edit = static_cast(editor);
QString str = edit->text();
//closeEditor(edit, QAbstractItemDelegate::NoHint);
if (model->setData(index, str))
model->setData(index, str, Qt::UserRole+DRole_CHP_STEMP);
else
model->setData(index, str, Qt::UserRole+DRole_CHP_STEMP);
}
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
Q_UNUSED(index);
int deviate = DIR_DEVIATE-2;
if (!(index.data(Qt::UserRole+DRole_CHP_ISROLL).toBool()))
deviate += DIR_DEVIATE_ADD;
if (us->round_view)
{
QString text= getItemText(index);
QFontMetrics fm(editor->font());
int delta = fm.height()/2;
deviate -= delta;
}
QRect rect = option.rect;
rect.setLeft(rect.left()+deviate);
editor->setGeometry(rect);
}
QLineEdit* getEditor()
{
return rename_edit;
}
private :
QLineEdit* rename_edit;
};
使用這個代理后,發現效果還是挺好看的,比原先不知道好了多少。
總結
以上是生活随笔為你收集整理的git 使用writer_GitHub - Vpredictor/WriterFly: [QT/C++] 写作天下,为作家创造世界而生,执云作笔,诉尽平生意。...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 第一、二章 引论、算法分析
- 下一篇: webpack+vue+mui学习心得