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
{
57 uint wordidx
; // first word
60 //__gshared LayUrl[uint] layUrlList; // for each word
61 private __gshared LayUrl
[] layUrlArray
;
63 string
findUrlByWordIndex (uint widx
) {
64 foreach (const ref LayUrl u
; layUrlArray
) if (u
.wordidx
== widx
) return u
.url
;
68 private void appendUrl (uint widx
, string url
) {
69 foreach (ref LayUrl u
; layUrlArray
) {
70 if (u
.wordidx
== widx
) {
75 layUrlArray
.length
+= 1;
76 layUrlArray
.assumeSafeAppend
;
77 layUrlArray
[$-1].url
= url
;
78 layUrlArray
[$-1].wordidx
= widx
;
81 private void urlWordsRemoved (uint widx
, uint count
) {
82 if (count
== 0) return;
83 for (usize idx
= 0; idx
< layUrlArray
.length
; ++idx
) {
84 if (layUrlArray
[idx
].wordidx
< widx
) continue;
85 // remove if removed ;-)
86 if (layUrlArray
[idx
].wordidx
< widx
+count
) {
87 layUrlArray
.unsafeArrayRemove(idx
);
91 layUrlArray
[idx
].wordidx
-= count
;
96 // ////////////////////////////////////////////////////////////////////////// //
97 class MessageStart
: LayObject
{
98 long msgid
; // >0: outgoing, unacked yet
100 this (long aid
=-1) nothrow @safe @nogc { msgid
= aid
; }
102 override int width () => 0;
103 override int spacewidth () => 0;
104 override int height () => 0;
105 override int ascent () => 0;
106 override int descent () => 0;
107 override bool canbreak () => true;
108 override bool spaced () => false;
110 override void draw (NVGContext ctx
, float x
, float y
) {}
114 class MessageDividerStart
: LayObject
{
115 long msgid
; // >0: outgoing, unacked yet
117 this (long aid
=-1) nothrow @safe @nogc { msgid
= aid
; }
119 override int width () => 0;
120 override int spacewidth () => 0;
121 override int height () => 0;
122 override int ascent () => 0;
123 override int descent () => 0;
124 override bool canbreak () => true;
125 override bool spaced () => false;
127 override void draw (NVGContext ctx
, float x
, float y
) {}
131 class MessageOutMark
: LayObject
{
132 long msgid
; // >=0: outgoing, unacked yet (0: sent in offline mode)
133 TextDigest digest
; // text digest
136 this (long aid
, SysTime atime
, const(void)[] atext
) nothrow @safe @nogc { msgid
= aid
; time
= atime
; digest
= textDigest(atext
); }
138 override int width () => kittyOut
.width
;
139 override int spacewidth () => 4;
140 override int height () => kittyOut
.height
;
141 override int ascent () => 0;
142 override int descent () => 0;
143 override bool canbreak () => true;
144 override bool spaced () => false;
146 override void draw (NVGContext ctx
, float x
, float y
) {
149 scope(exit
) nvg
.restore
;
152 nvg
.rect(x
, y
-height
+3, width
, height
);
153 nvg
.fillPaint(nvg
.imagePattern(x
, y
-height
+3, width
, height
, 0, kittyOut
));
158 final bool isOurMark (const(void)[] atext
, SysTime atime
) nothrow @trusted {
159 pragma(inline
, true);
160 return (atime
== time
&& digest
[] == textDigest(atext
));
163 final bool isOurMark (long aid
, const(void)[] atext
, SysTime atime
) nothrow @trusted {
164 pragma(inline
, true);
165 return (aid
> 0 ?
(msgid
== aid
) : (atime
== time
&& digest
[] == textDigest(atext
)));
170 // ////////////////////////////////////////////////////////////////////////// //
171 void logFixAckMessageId (long oldid
, long newid
, const(void)[] text
, SysTime time
) {
172 if (oldid
== newid
) return; // nothing to do
173 foreach (immutable uint widx
; 0..lay
.wordCount
) {
174 auto w
= lay
.wordByIndex(widx
);
175 int oidx
= w
.objectIdx
;
177 if (auto maw
= cast(MessageOutMark
)lay
.objectAtIndex(oidx
)) {
178 if (maw
.isOurMark(oldid
, text
, time
)) {
180 glconPostScreenRepaint(); // redraw
187 void logResetAckMessageIds () {
188 bool doRefresh
= false;
189 foreach (immutable uint widx
; 0..lay
.wordCount
) {
190 auto w
= lay
.wordByIndex(widx
);
191 int oidx
= w
.objectIdx
;
193 if (auto maw
= cast(MessageOutMark
)lay
.objectAtIndex(oidx
)) {
201 if (doRefresh
) glconPostScreenRepaint(); // redraw
204 void ackLogMessage (long msgid
) {
205 if (msgid
<= 0) return;
206 foreach (immutable uint widx
; 0..lay
.wordCount
) {
207 auto w
= lay
.wordByIndex(widx
);
208 int oidx
= w
.objectIdx
;
210 if (auto maw
= cast(MessageOutMark
)lay
.objectAtIndex(oidx
)) {
211 if (maw
.msgid
== msgid
) {
212 maw
.msgid
= -1; // reset mark
213 glconPostScreenRepaint(); // redraw
221 // coordinates are adjusted so (0, 0) points to logical layouter top-left
223 bool logCheckCancelMark (int mx, int my) {
224 bool removeFromResendQueue (long msgid, in ref TextDigest digest, SysTime time) {
228 // ////////////////////////////////////////////////////////////////////////// //
230 lay
.wipeAll(true); // clear log, but delete objects
231 layUrlArray
.unsafeArrayClear();
236 // ////////////////////////////////////////////////////////////////////////// //
237 void addDividerLine (bool doflushgui
=false) {
238 if (glconCtlWindow
is null || glconCtlWindow
.closed
) return;
241 bool inFrame
= nvg
.inFrame
;
243 glconCtlWindow
.setAsCurrentOpenGlContext(); // make this window active
244 nvg
.beginFrame(glconCtlWindow
.width
, glconCtlWindow
.height
);
249 if (doflushgui
) flushGui();
250 glconCtlWindow
.releaseCurrentOpenGlContext();
255 while (widx
< lay
.wordCount
) {
256 auto w
= lay
.wordByIndex(widx
);
257 int oidx
= w
.objectIdx
;
259 if (auto maw
= cast(MessageDividerStart
)lay
.objectAtIndex(oidx
)) {
260 // this always followed by expander; remove them both
261 lay
.removeWordsAt(widx
, 2);
262 urlWordsRemoved(widx
, 2);
269 lay
.fontStyle
.fontsize
= 2;
270 lay
.fontStyle
.color
= NVGColor
.k8orange
.asUint
;
271 lay
.fontStyle
.bgcolor
= NVGColor("#aa0").asUint
;
272 lay
.fontStyle
.monospace
= true;
274 lay
.putObject(new MessageDividerStart());
281 glconPostScreenRepaint();
285 // ////////////////////////////////////////////////////////////////////////// //
286 // `ct` can be `null` for "my message" or "system message"
287 void addTextToLog (Account acc
, Contact ct
, LogFile
.Msg
.Kind kind
, bool action
, const(char)[] msg
, SysTime time
, long msgid
=-1, bool doflushgui
=false) {
288 if (glconCtlWindow
is null || glconCtlWindow
.closed
) return;
291 bool inFrame
= nvg
.inFrame
;
293 glconCtlWindow
.setAsCurrentOpenGlContext(); // make this window active
294 nvg
.beginFrame(glconCtlWindow
.width
, glconCtlWindow
.height
);
299 if (doflushgui
) flushGui();
300 glconCtlWindow
.releaseCurrentOpenGlContext();
304 // add "message start" mark
305 lay
.putObject(new MessageStart(msgid
));
307 lay
.fontStyle
.fontsize
= 16;
308 lay
.fontStyle
.color
= NVGColor
.k8orange
.asUint
;
309 //lay.fontStyle.bgcolor = NVGColor("#222").asUint;
310 lay
.fontStyle
.bgcolor
= NVGColor("#5888").asUint
;
311 lay
.fontStyle
.monospace
= true;
315 final switch (kind
) {
316 case LogFile
.Msg
.Kind
.Outgoing
: textColor
= NVGColor
.k8orange
; lay
.fontStyle
.color
= NVGColor("#c40").asUint
; lay
.put(acc
.info
.nick
); break;
317 case LogFile
.Msg
.Kind
.Incoming
: textColor
= NVGColor("#aaa"); lay
.fontStyle
.color
= NVGColor("#777").asUint
; lay
.put(ct
.info
.nick
); break;
318 case LogFile
.Msg
.Kind
.Notification
: textColor
= NVGColor("#0c0"); lay
.fontStyle
.color
= textColor
.asUint
; lay
.put("*system*"); break;
321 lay
.fontStyle
.monospace
= false;
322 //lay.putHardSpace(64);
324 // add "message outgoing" mark
325 if (kind
== LogFile
.Msg
.Kind
.Outgoing
) {
326 //conwriteln("OG: msgid=", msgid);
327 if (msgid
<= 0 && ct
!is null) {
328 msgid
= ct
.findInResendQueue(msg
, time
);
329 //conwriteln(" new msgid=", msgid);
331 lay
.putObject(new MessageOutMark(msgid
, time
, msg
));
335 if (kind
== LogFile
.Msg
.Kind
.Incoming
) lay
.fontStyle
.color
= NVGColor("#888").asUint
;
338 import std
.format
: format
;
339 auto dt = cast(DateTime
)time
;
340 string tstr
= "%04u/%02u/%02u".format(dt.year
, dt.month
, dt.day
);
345 lay
.fontStyle
.color
= (kind
== LogFile
.Msg
.Kind
.Incoming ?
NVGColor("#888").asUint
: NVGColor("#666").asUint
);
346 lay
.fontStyle
.bgcolor
= NVGColor("#006").asUint
;
349 import std
.format
: format
;
350 auto dt = cast(DateTime
)time
;
351 string tstr
= "%02u:%02u:%02u".format(dt.hour
, dt.minute
, dt.second
);
357 lay
.fontStyle
.bgcolor
= NVGColor
.transparent
.asUint
;
358 lay
.fontStyle
.fontsize
= 20;
361 lay
.fontStyle
.color
= NVGColor("#fff").asUint
;
365 void xput (const(char)[] str) {
367 auto nl
= str.indexOf('\n');
368 if (nl
< 0) { lay
.put(str); break; }
369 if (nl
> 0) lay
.put(str[0..nl
]);
375 msg
= msg
.xstripright
;
377 lay
.fontStyle
.color
= textColor
.asUint
;
379 auto nfo
= urlDetect(msg
);
385 xput(msg
[0..nfo
.pos
]);
386 string url
= msg
[nfo
.pos
..nfo
.end
].idup
;
387 msg
= msg
[nfo
.end
..$];
388 auto stword
= lay
.nextWordIndex
;
390 auto c
= lay
.fontStyle
.color
;
391 scope(exit
) { lay
.popStyles
; lay
.fontStyle
.color
= c
; }
392 lay
.fontStyle
.href
= true;
393 lay
.fontStyle
.underline
= true;
394 lay
.fontStyle
.color
= NVGColor("#06f").asUint
;
396 while (stword
< lay
.nextWordIndex
) {
397 appendUrl(stword
, url
);
406 glconPostScreenRepaint();
410 void addTextToLog (Account acc
, Contact ct
, in ref LogFile
.Msg msg
, long msgid
=-1, bool doflushgui
=false) {
411 import iv
.utfutil
: utf8Encode
;
414 scope(exit
) delete text
;
416 foreach (dchar dc
; msg
.byDChar
) {
418 auto len
= utf8Encode(buf
[], dc
);
421 addTextToLog(acc
, ct
, msg
.kind
, msg
.isMe
, text
, msg
.time
, msgid
, doflushgui
);