1 /* coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
2 * Understanding is not required. Only obedience.
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, version 3 of the License ONLY.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 module tklog
is aliced
;
22 import arsd
.simpledisplay
;
27 import iv
.nanovega
.blendish
;
28 import iv
.nanovega
.textlayouter
;
46 // ////////////////////////////////////////////////////////////////////////// //
47 alias LayTextClass
= LayTextD
;
50 // ////////////////////////////////////////////////////////////////////////// //
51 __gshared LayTextClass lay
;
52 __gshared
int layWinHeight
= 0;
53 __gshared
int layOffset
= 0; // from bottom
55 static struct LayUrl
{
59 usize nextFree
; // index+1
62 @property bool isFree () const nothrow @safe @nogc => (url
.length
== 0);
65 private __gshared usize
[string
] layUrlIndex
;
66 private __gshared usize layUrlFreeHead
= 0;
67 private __gshared LayUrl
[] layUrlArray
;
70 private usize
allocUrl (string url
) {
71 usize res
= layUrlFreeHead
;
74 layUrlFreeHead
= layUrlArray
[res
].nextFree
;
76 res
= layUrlArray
.length
;
77 layUrlArray
.length
+= 1;
78 layUrlArray
.assumeSafeAppend
;
80 layUrlArray
[res
].url
= url
;
81 layUrlArray
[res
].refcount
= 1;
82 layUrlIndex
[url
] = res
;
87 private void freeUrl (usize idx
) {
88 assert(idx
< layUrlArray
.length
);
89 assert(!layUrlArray
[idx
].isFree
);
90 if (--layUrlArray
[idx
].refcount
) return;
91 //conwriteln("FREE URL idx=", idx, "; url=[", layUrlArray[idx].url, "]");
92 layUrlIndex
.remove(layUrlArray
[idx
].url
);
93 layUrlArray
[idx
].url
= null;
94 layUrlArray
[idx
].nextFree
= layUrlFreeHead
;
95 layUrlFreeHead
= idx
+1;
99 string
findUrlByWordIndex (uint widx
) {
100 auto w
= lay
.wordByIndex(widx
);
101 if (w
is null) return null;
103 if (pos
< 1 || pos
> layUrlArray
.length
) return null;
104 //conwriteln(" pos=", pos, "; url=[", layUrlArray[pos-1].url, "]");
106 return layUrlArray
[pos
].url
;
110 private void appendUrl (uint widx
, string url
) {
111 auto w
= lay
.wordByIndex(widx
);
112 if (w
is null) return;
113 assert(w
.udata
== 0);
114 if (url
.length
== 0) return;
115 auto ip
= url
in layUrlIndex
;
117 assert(!layUrlArray
[*ip
].isFree
);
118 ++layUrlArray
[*ip
].refcount
;
120 //conwriteln("OLD URL widx=", widx, "; udata=", w.udata, "; rc=", layUrlArray[*ip].refcount, "; url=[", layUrlArray[*ip].url, "]");
122 usize pos
= allocUrl(url
);
124 //conwriteln("NEW URL widx=", widx, "; udata=", w.udata, "; rc=", layUrlArray[pos].refcount, "; url=[", layUrlArray[pos].url, "]");
129 private void urlWordsRemoved (uint widx
, int count
) {
130 if (count
< 1) return;
131 foreach (uint wpos
; widx
..widx
+count
) {
132 auto w
= lay
.wordByIndex(wpos
);
133 if (w
is null) continue;
137 assert(pos
< layUrlArray
.length
);
138 assert(!layUrlArray
[pos
].isFree
);
139 assert(layUrlArray
[pos
].refcount
);
140 //conwriteln("FREE WORD URL widx=", wpos, "; udata=", w.udata, "; rc=", layUrlArray[pos].refcount, "; url=[", layUrlArray[pos].url, "]");
146 private void urlWipeAll () {
147 layUrlArray
.unsafeArrayClear();
153 // ////////////////////////////////////////////////////////////////////////// //
154 class MessageStart
: LayObject
{
155 long msgid
; // >0: outgoing, unacked yet
157 this (long aid
=-1) nothrow @safe @nogc { msgid
= aid
; }
159 override int width () => 0;
160 override int spacewidth () => 0;
161 override int height () => 0;
162 override int ascent () => 0;
163 override int descent () => 0;
164 override bool canbreak () => true;
165 override bool spaced () => false;
167 override void draw (NVGContext ctx
, float x
, float y
) {}
171 class MessageDividerStart
: LayObject
{
172 long msgid
; // >0: outgoing, unacked yet
174 this (long aid
=-1) nothrow @safe @nogc { msgid
= aid
; }
176 override int width () => 0;
177 override int spacewidth () => 0;
178 override int height () => 0;
179 override int ascent () => 0;
180 override int descent () => 0;
181 override bool canbreak () => true;
182 override bool spaced () => false;
184 override void draw (NVGContext ctx
, float x
, float y
) {}
188 class MessageOutMark
: LayObject
{
189 long msgid
; // >=0: outgoing, unacked yet (0: sent in offline mode)
190 TextDigest digest
; // text digest
193 this (long aid
, SysTime atime
, const(void)[] atext
) nothrow @safe @nogc { msgid
= aid
; time
= atime
; digest
= textDigest(atext
); }
195 override int width () => kittyOut
.width
;
196 override int spacewidth () => 4;
197 override int height () => kittyOut
.height
;
198 override int ascent () => 0;
199 override int descent () => 0;
200 override bool canbreak () => true;
201 override bool spaced () => false;
203 override void draw (NVGContext ctx
, float x
, float y
) {
206 scope(exit
) nvg
.restore
;
209 nvg
.rect(x
, y
-height
+3, width
, height
);
210 nvg
.fillPaint(nvg
.imagePattern(x
, y
-height
+3, width
, height
, 0, kittyOut
));
215 final bool isOurMark (const(void)[] atext
, SysTime atime
) nothrow @trusted {
216 pragma(inline
, true);
217 return (atime
== time
&& digest
[] == textDigest(atext
));
220 final bool isOurMark (long aid
, const(void)[] atext
, SysTime atime
) nothrow @trusted {
221 pragma(inline
, true);
222 return (aid
> 0 ?
(msgid
== aid
) : (atime
== time
&& digest
[] == textDigest(atext
)));
227 // ////////////////////////////////////////////////////////////////////////// //
228 void logFixAckMessageId (long oldid
, long newid
, const(void)[] text
, SysTime time
) {
229 if (oldid
== newid
) return; // nothing to do
230 foreach (immutable uint widx
; 0..lay
.wordCount
) {
231 auto w
= lay
.wordByIndex(widx
);
232 int oidx
= w
.objectIdx
;
234 if (auto maw
= cast(MessageOutMark
)lay
.objectAtIndex(oidx
)) {
235 if (maw
.isOurMark(oldid
, text
, time
)) {
237 glconPostScreenRepaint(); // redraw
244 void logResetAckMessageIds () {
245 bool doRefresh
= false;
246 foreach (immutable uint widx
; 0..lay
.wordCount
) {
247 auto w
= lay
.wordByIndex(widx
);
248 int oidx
= w
.objectIdx
;
250 if (auto maw
= cast(MessageOutMark
)lay
.objectAtIndex(oidx
)) {
258 if (doRefresh
) glconPostScreenRepaint(); // redraw
261 void ackLogMessage (long msgid
) {
262 if (msgid
<= 0) return;
263 foreach (immutable uint widx
; 0..lay
.wordCount
) {
264 auto w
= lay
.wordByIndex(widx
);
265 int oidx
= w
.objectIdx
;
267 if (auto maw
= cast(MessageOutMark
)lay
.objectAtIndex(oidx
)) {
268 if (maw
.msgid
== msgid
) {
269 maw
.msgid
= -1; // reset mark
270 glconPostScreenRepaint(); // redraw
278 // coordinates are adjusted so (0, 0) points to logical layouter top-left
280 bool logCheckCancelMark (int mx, int my) {
281 bool removeFromResendQueue (long msgid, in ref TextDigest digest, SysTime time) {
285 // ////////////////////////////////////////////////////////////////////////// //
287 lay
.wipeAll(true); // clear log, but delete objects
293 // ////////////////////////////////////////////////////////////////////////// //
294 void addDividerLine (bool doflushgui
=false) {
295 if (glconCtlWindow
is null || glconCtlWindow
.closed
) return;
298 bool inFrame
= nvg
.inFrame
;
300 glconCtlWindow
.setAsCurrentOpenGlContext(); // make this window active
301 glViewport(0, 0, glconCtlWindow
.width
, glconCtlWindow
.height
);
302 nvg
.beginFrame(glconCtlWindow
.width
, glconCtlWindow
.height
);
307 if (doflushgui
) flushGui();
308 glconCtlWindow
.releaseCurrentOpenGlContext();
313 while (widx
< lay
.wordCount
) {
314 auto w
= lay
.wordByIndex(widx
);
315 int oidx
= w
.objectIdx
;
317 if (auto maw
= cast(MessageDividerStart
)lay
.objectAtIndex(oidx
)) {
318 // this always followed by expander; remove them both
319 lay
.removeWordsAt(widx
, 2);
320 urlWordsRemoved(widx
, 2);
327 lay
.fontStyle
.fontsize
= 2;
328 lay
.fontStyle
.color
= NVGColor
.k8orange
.asUint
;
329 lay
.fontStyle
.bgcolor
= NVGColor("#aa0").asUint
;
330 lay
.fontStyle
.monospace
= true;
332 lay
.putObject(new MessageDividerStart());
339 glconPostScreenRepaint();
343 // ////////////////////////////////////////////////////////////////////////// //
344 // `ct` can be `null` for "my message" or "system message"
345 void addTextToLog (Account acc
, Contact ct
, LogFile
.Msg
.Kind kind
, bool action
,
346 const(char)[] msg
, SysTime time
, long msgid
=-1, bool doflushgui
=false)
348 if (glconCtlWindow
is null || glconCtlWindow
.closed
) return;
351 bool inFrame
= nvg
.inFrame
;
353 glconCtlWindow
.setAsCurrentOpenGlContext(); // make this window active
354 nvg
.beginFrame(glconCtlWindow
.width
, glconCtlWindow
.height
);
359 if (doflushgui
) flushGui();
360 glconCtlWindow
.releaseCurrentOpenGlContext();
366 // add "message start" mark
367 lay
.putObject(new MessageStart(msgid
));
369 lay
.fontStyle
.fontsize
= 16;
370 lay
.fontStyle
.color
= NVGColor
.k8orange
.asUint
;
371 //lay.fontStyle.bgcolor = NVGColor("#222").asUint;
372 lay
.fontStyle
.bgcolor
= NVGColor("#5888").asUint
;
373 lay
.fontStyle
.monospace
= true;
378 final switch (kind
) {
379 case LogFile
.Msg
.Kind
.Outgoing
:
380 textColor
= NVGColor
.k8orange
; lay
.fontStyle
.color
= NVGColor("#c40").asUint
;
381 lay
.put(acc
.info
.nick
);
384 case LogFile
.Msg
.Kind
.Incoming
:
385 textColor
= NVGColor("#aaa");
386 lay
.fontStyle
.color
= NVGColor("#777").asUint
;
387 lay
.put(ct
.info
.nick
);
390 case LogFile
.Msg
.Kind
.Notification
:
391 textColor
= NVGColor("#0c0");
392 lay
.fontStyle
.color
= textColor
.asUint
;
398 lay
.fontStyle
.monospace
= false;
399 //lay.putHardSpace(64);
401 // add "message outgoing" mark
402 if (kind
== LogFile
.Msg
.Kind
.Outgoing
) {
403 //conwriteln("OG: msgid=", msgid);
404 if (msgid
<= 0 && ct
!is null) {
405 msgid
= ct
.findInResendQueue(msg
, time
);
406 //conwriteln(" new msgid=", msgid);
408 lay
.putObject(new MessageOutMark(msgid
, time
, msg
));
413 if (kind
== LogFile
.Msg
.Kind
.Incoming
) lay
.fontStyle
.color
= NVGColor("#888").asUint
;
416 import std
.format
: format
;
417 auto dt = cast(DateTime
)time
;
418 string tstr
= "%04u/%02u/%02u".format(dt.year
, dt.month
, dt.day
);
424 lay
.fontStyle
.color
= (kind
== LogFile
.Msg
.Kind
.Incoming ?
NVGColor("#888").asUint
: NVGColor("#666").asUint
);
425 lay
.fontStyle
.bgcolor
= NVGColor("#006").asUint
;
428 import std
.format
: format
;
429 auto dt = cast(DateTime
)time
;
430 string tstr
= "%02u:%02u:%02u".format(dt.hour
, dt.minute
, dt.second
);
437 lay
.fontStyle
.bgcolor
= NVGColor
.transparent
.asUint
;
438 lay
.fontStyle
.fontsize
= fontSize
;
442 lay
.fontStyle
.color
= NVGColor("#fff").asUint
;
446 static bool isWordDelim (dchar ch
) {
447 import std
.uni
: isWhite
;
449 ch
== lay
.EndLineCh || ch
== lay
.EndParaCh ||
450 //ch == lay.NBSpaceCh || ch == lay.NarrowNBSpaceCh ||
451 ch
== lay
.SoftHyphenCh ||
452 ch
<= ' ' ||
isWhite(ch
);
455 void layPutSplitLongWords (const(char)[] str) {
456 enum MaxCharsSplit
= 16;
459 int lastWordLength
= 0;
461 dchar curCh
= dec.decode(cast(ubyte)str[0]);
463 if (curCh
> dchar.max
) continue;
464 if (isWordDelim(curCh
)) {
467 if (++lastWordLength
>= MaxCharsSplit
) {
476 bool wasNL
= !action
;
477 bool inQuote
= false;
479 void setFontStyle () {
481 lay
.fontStyle
.color
= NVGColor("#ff0").asUint
;
482 lay
.fontStyle
.fontsize
= fontSize
-1;
483 lay
.fontStyle
.italic
= true;
485 lay
.fontStyle
.color
= textColor
.asUint
;
486 lay
.fontStyle
.fontsize
= fontSize
;
487 lay
.fontStyle
.italic
= false;
491 void checkQuote (const(char)[] str) {
493 inQuote
= (str[0] == '>');
498 void xput (const(char)[] str) {
501 auto nl
= str.indexOf('\n');
503 layPutSplitLongWords(str);
507 layPutSplitLongWords(str[0..nl
]);
516 msg
= msg
.xstripright
;
518 lay
.fontStyle
.color
= textColor
.asUint
;
520 auto nfo
= urlDetect(msg
);
526 xput(msg
[0..nfo
.pos
]);
527 string url
= msg
[nfo
.pos
..nfo
.end
].idup
;
528 msg
= msg
[nfo
.end
..$];
529 auto stword
= lay
.nextWordIndex
;
531 auto c
= lay
.fontStyle
.color
;
532 scope(exit
) { lay
.popStyles
; lay
.fontStyle
.color
= c
; }
533 lay
.fontStyle
.href
= true;
534 lay
.fontStyle
.underline
= true;
535 lay
.fontStyle
.color
= NVGColor("#06f").asUint
;
538 lay
.endWord(spaced
:(msg
[0] <= ' ' && msg
[0] != '\n'));
540 lay
.endWord(spaced
:false);
542 while (stword
< lay
.nextWordIndex
) {
543 appendUrl(stword
, url
);
553 glconPostScreenRepaint();
557 void addTextToLog (Account acc
, Contact ct
, in ref LogFile
.Msg msg
, long msgid
=-1, bool doflushgui
=false) {
558 import iv
.utfutil
: utf8Encode
;
561 scope(exit
) delete text
;
563 foreach (dchar dc
; msg
.byDChar
) {
565 auto len
= utf8Encode(buf
[], dc
);
568 addTextToLog(acc
, ct
, msg
.kind
, msg
.isMe
, text
, msg
.time
, msgid
, doflushgui
);