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, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 module tklog
is aliced
;
23 import arsd
.simpledisplay
;
28 import iv
.nanovega
.blendish
;
29 import iv
.nanovega
.textlayouter
;
47 // ////////////////////////////////////////////////////////////////////////// //
48 alias LayTextClass
= LayTextD
;
51 // ////////////////////////////////////////////////////////////////////////// //
52 __gshared LayTextClass lay
;
53 __gshared
int layWinHeight
= 0;
54 __gshared
int layOffset
= 0; // from bottom
56 static struct LayUrl
{
58 uint wordidx
; // first word
61 __gshared LayUrl
[uint] layUrlList
; // for each word
64 // ////////////////////////////////////////////////////////////////////////// //
65 class MessageStart
: LayObject
{
66 long msgid
; // >0: outgoing, unacked yet
68 this (long aid
=-1) nothrow @safe @nogc { msgid
= aid
; }
70 override int width () => 0;
71 override int spacewidth () => 0;
72 override int height () => 0;
73 override int ascent () => 0;
74 override int descent () => 0;
75 override bool canbreak () => true;
76 override bool spaced () => false;
78 override void draw (NVGContext ctx
, float x
, float y
) {}
82 class MessageDividerStart
: LayObject
{
83 long msgid
; // >0: outgoing, unacked yet
85 this (long aid
=-1) nothrow @safe @nogc { msgid
= aid
; }
87 override int width () => 0;
88 override int spacewidth () => 0;
89 override int height () => 0;
90 override int ascent () => 0;
91 override int descent () => 0;
92 override bool canbreak () => true;
93 override bool spaced () => false;
95 override void draw (NVGContext ctx
, float x
, float y
) {}
99 class MessageOutMark
: LayObject
{
100 long msgid
; // >=0: outgoing, unacked yet (0: sent in offline mode)
101 TextDigest digest
; // text digest
104 this (long aid
, SysTime atime
, const(void)[] atext
) nothrow @safe @nogc { msgid
= aid
; time
= atime
; digest
= textDigest(atext
); }
106 override int width () => kittyOut
.width
;
107 override int spacewidth () => 4;
108 override int height () => kittyOut
.height
;
109 override int ascent () => 0;
110 override int descent () => 0;
111 override bool canbreak () => true;
112 override bool spaced () => false;
114 override void draw (NVGContext ctx
, float x
, float y
) {
117 scope(exit
) nvg
.restore
;
120 nvg
.rect(x
, y
-height
+3, width
, height
);
121 nvg
.fillPaint(nvg
.imagePattern(x
, y
-height
+3, width
, height
, 0, kittyOut
));
126 final bool isOurMark (const(void)[] atext
, SysTime atime
) nothrow @trusted {
127 pragma(inline
, true);
128 return (atime
== time
&& digest
[] == textDigest(atext
));
131 final bool isOurMark (long aid
, const(void)[] atext
, SysTime atime
) nothrow @trusted {
132 pragma(inline
, true);
133 return (aid
> 0 ?
(msgid
== aid
) : (atime
== time
&& digest
[] == textDigest(atext
)));
138 // ////////////////////////////////////////////////////////////////////////// //
139 void logFixAckMessageId (long oldid
, long newid
, const(void)[] text
, SysTime time
) {
140 if (oldid
== newid
) return; // nothing to do
141 foreach (immutable uint widx
; 0..lay
.wordCount
) {
142 auto w
= lay
.wordByIndex(widx
);
143 int oidx
= w
.objectIdx
;
145 if (auto maw
= cast(MessageOutMark
)lay
.objectAtIndex(oidx
)) {
146 if (maw
.isOurMark(oldid
, text
, time
)) {
148 glconPostScreenRepaint(); // redraw
155 void logResetAckMessageIds () {
156 foreach (immutable uint widx
; 0..lay
.wordCount
) {
157 auto w
= lay
.wordByIndex(widx
);
158 int oidx
= w
.objectIdx
;
160 if (auto maw
= cast(MessageOutMark
)lay
.objectAtIndex(oidx
)) {
162 glconPostScreenRepaint(); // redraw
168 void ackLogMessage (long msgid
) {
169 if (msgid
<= 0) return;
170 foreach (immutable uint widx
; 0..lay
.wordCount
) {
171 auto w
= lay
.wordByIndex(widx
);
172 int oidx
= w
.objectIdx
;
174 if (auto maw
= cast(MessageOutMark
)lay
.objectAtIndex(oidx
)) {
175 if (maw
.msgid
== msgid
) {
176 maw
.msgid
= -1; // reset mark
177 glconPostScreenRepaint(); // redraw
185 // coordinates are adjusted so (0, 0) points to logical layouter top-left
187 bool logCheckCancelMark (int mx, int my) {
188 bool removeFromResendQueue (long msgid, in ref TextDigest digest, SysTime time) {
192 // ////////////////////////////////////////////////////////////////////////// //
194 lay
.wipeAll(true); // clear log, but delete objects
200 // ////////////////////////////////////////////////////////////////////////// //
201 void addDividerLine (bool doflushgui
=false) {
202 if (glconCtlWindow
is null || glconCtlWindow
.closed
) return;
205 bool inFrame
= nvg
.inFrame
;
207 glconCtlWindow
.setAsCurrentOpenGlContext(); // make this window active
208 nvg
.beginFrame(glconCtlWindow
.width
, glconCtlWindow
.height
);
213 if (doflushgui
) flushGui();
214 glconCtlWindow
.releaseCurrentOpenGlContext();
219 while (widx
< lay
.wordCount
) {
220 auto w
= lay
.wordByIndex(widx
);
221 int oidx
= w
.objectIdx
;
223 if (auto maw
= cast(MessageDividerStart
)lay
.objectAtIndex(oidx
)) {
224 // this always followed by expander; remove them both
225 lay
.removeWordsAt(widx
, 2);
232 lay
.fontStyle
.fontsize
= 2;
233 lay
.fontStyle
.color
= NVGColor
.k8orange
.asUint
;
234 lay
.fontStyle
.bgcolor
= NVGColor("#aa0").asUint
;
235 lay
.fontStyle
.monospace
= true;
237 lay
.putObject(new MessageDividerStart());
244 glconPostScreenRepaint();
248 // ////////////////////////////////////////////////////////////////////////// //
249 // `ct` can be `null` for "my message" or "system message"
250 void addTextToLog (Account acc
, Contact ct
, LogFile
.Msg
.Kind kind
, bool action
, const(char)[] msg
, SysTime time
, long msgid
=-1, bool doflushgui
=false) {
251 if (glconCtlWindow
is null || glconCtlWindow
.closed
) return;
254 bool inFrame
= nvg
.inFrame
;
256 glconCtlWindow
.setAsCurrentOpenGlContext(); // make this window active
257 nvg
.beginFrame(glconCtlWindow
.width
, glconCtlWindow
.height
);
262 if (doflushgui
) flushGui();
263 glconCtlWindow
.releaseCurrentOpenGlContext();
267 // add "message start" mark
268 lay
.putObject(new MessageStart(msgid
));
270 lay
.fontStyle
.fontsize
= 16;
271 lay
.fontStyle
.color
= NVGColor
.k8orange
.asUint
;
272 lay
.fontStyle
.bgcolor
= NVGColor("#222").asUint
;
273 lay
.fontStyle
.monospace
= true;
277 final switch (kind
) {
278 case LogFile
.Msg
.Kind
.Outgoing
: textColor
= NVGColor
.k8orange
; lay
.fontStyle
.color
= NVGColor("#c40").asUint
; lay
.put(acc
.info
.nick
); break;
279 case LogFile
.Msg
.Kind
.Incoming
: textColor
= NVGColor("#ccc"); lay
.fontStyle
.color
= NVGColor("#666").asUint
; lay
.put(ct
.info
.nick
); break;
280 case LogFile
.Msg
.Kind
.Notification
: textColor
= NVGColor("#0c0"); lay
.fontStyle
.color
= textColor
.asUint
; lay
.put("*system*"); break;
283 lay
.fontStyle
.monospace
= false;
284 //lay.putHardSpace(64);
286 // add "message outgoing" mark
287 if (kind
== LogFile
.Msg
.Kind
.Outgoing
) {
288 //conwriteln("OG: msgid=", msgid);
290 msgid
= ct
.findInResendQueue(msg
, time
);
291 //conwriteln(" new msgid=", msgid);
293 lay
.putObject(new MessageOutMark(msgid
, time
, msg
));
297 if (kind
== LogFile
.Msg
.Kind
.Incoming
) lay
.fontStyle
.color
= NVGColor("#888").asUint
;
300 import std
.format
: format
;
301 auto dt = cast(DateTime
)time
;
302 string tstr
= "%04u/%02u/%02u".format(dt.year
, dt.month
, dt.day
);
307 lay
.fontStyle
.color
= (kind
== LogFile
.Msg
.Kind
.Incoming ?
NVGColor("#888").asUint
: NVGColor("#666").asUint
);
308 lay
.fontStyle
.bgcolor
= NVGColor("#006").asUint
;
311 import std
.format
: format
;
312 auto dt = cast(DateTime
)time
;
313 string tstr
= "%02u:%02u:%02u".format(dt.hour
, dt.minute
, dt.second
);
319 lay
.fontStyle
.bgcolor
= NVGColor
.transparent
.asUint
;
320 lay
.fontStyle
.fontsize
= 20;
323 lay
.fontStyle
.color
= NVGColor("#fff").asUint
;
327 void xput (const(char)[] str) {
329 auto nl
= str.indexOf('\n');
330 if (nl
< 0) { lay
.put(str); break; }
331 if (nl
> 0) lay
.put(str[0..nl
]);
337 msg
= msg
.xstripright
;
339 lay
.fontStyle
.color
= textColor
.asUint
;
341 auto nfo
= urlDetect(msg
);
347 xput(msg
[0..nfo
.pos
]);
348 string url
= msg
[nfo
.pos
..nfo
.end
].idup
;
349 msg
= msg
[nfo
.end
..$];
350 auto stword
= lay
.nextWordIndex
;
352 auto c
= lay
.fontStyle
.color
;
353 scope(exit
) { lay
.popStyles
; lay
.fontStyle
.color
= c
; }
354 lay
.fontStyle
.href
= true;
355 lay
.fontStyle
.underline
= true;
356 lay
.fontStyle
.color
= NVGColor("#06f").asUint
;
358 while (stword
< lay
.nextWordIndex
) {
359 layUrlList
[stword
] = LayUrl(url
, stword
);
368 glconPostScreenRepaint();
372 void addTextToLog (Account acc
, Contact ct
, in ref LogFile
.Msg msg
, long msgid
=-1, bool doflushgui
=false) {
373 import iv
.utfutil
: utf8Encode
;
376 scope(exit
) delete text
;
378 foreach (dchar dc
; msg
.byDChar
) {
380 auto len
= utf8Encode(buf
[], dc
);
383 addTextToLog(acc
, ct
, msg
.kind
, msg
.isMe
, text
, msg
.time
, msgid
, doflushgui
);