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 notifyicon
is aliced
;
22 import arsd
.simpledisplay
;
29 import iv
.nanovega
.textlayouter
;
42 // ////////////////////////////////////////////////////////////////////////// //
43 class TooltipWindow
: SimpleWindow
{
45 static string
charArrayToString(T
) (T
[] str) if (isAnyCharType
!(T
, true)) {
46 if (str.length
== 0) return null;
47 static if (isWideCharType
!(T
, true)) {
50 foreach (auto ch
; str) len
+= utf8Encode(ut
[], ch
);
53 foreach (auto ch
; str) {
54 auto l
= utf8Encode(ut
[], ch
);
60 static if (is(T
== immutable(char))) return str; else return str.idup
;
65 static GxRect
getWorkArea () {
67 getWorkAreaRect(rc
.x0
, rc
.y0
, rc
.width
, rc
.height
);
71 static GxSize
hintWindowSize(T
) (T
[] hinttext
) nothrow if (isAnyCharType
!(T
, true)) {
74 int textWidth
= laf
.textWidth(hinttext
)+6;
75 if (textWidth
< 8) textWidth
= 8;
76 return GxSize(textWidth
, laf
.textHeight
+4);
88 if (hintvg
!is null) {
89 this.setAsCurrentOpenGlContext();
90 scope(exit
) { flushGui(); this.releaseCurrentOpenGlContext(); }
92 assert(hintvg
is null);
93 //conwriteln("ONCLOSING!");
97 void cbWindowResized (int wdt
, int hgt
) {
98 if (this.closed
) return;
99 if (!this.visible
) return;
100 this.redrawOpenGlSceneNow();
104 this.setAsCurrentOpenGlContext(); // make this window active
105 scope(exit
) this.releaseCurrentOpenGlContext();
107 hintvg
= nvgCreateContext(NVGContextFlag
.FontNoAA
);
108 if (hintvg
is null) assert(0, "cannot initialize NanoVG");
109 //hintvg.createFont("ui", uiFontNames[0]);
110 //if (nvg !is null) hintvg.addFontsFrom(nvg); else hintvg.createFont("ui", uiFontNames[0]);
111 hintvg
.fonsContext
.addFontsFrom(fstash
);
112 //conwriteln("FIRST TIME");
113 //this.redrawOpenGlSceneNow();
117 if (this.closed
) return;
118 if (hintvg
!is null) {
120 this.setAsCurrentOpenGlContext(); // make this window active
121 scope(exit
) this.releaseCurrentOpenGlContext();
123 glViewport(0, 0, this.width
, this.height
);
124 glClearColor(0, 0, 0, 0);
125 glClear(glNVGClearFlags
/*|GL_COLOR_BUFFER_BIT*/);
127 hintvg
.beginFrame(this.width
, this.height
);
128 scope(exit
) hintvg
.endFrame();
129 //hintvg.shapeAntiAlias = false;
131 //auto wsz = hintWindowSize(hinttext);
132 //conwritefln!"sz=(%d,%d); wsz=(%d,%d); nsz=(%d,%d)"(this.width, this.height, wsz.width, wsz.height, hintvg.width, hintvg.height);
135 hintvg
.rect(0.5, 0.5, hintvg
.width
-1, hintvg
.height
-1);
136 hintvg
.strokeWidth
= 1;
137 hintvg
.strokeColor(NVGColor
.black
);
138 hintvg
.fillColor(NVGColor
.yellow
);
142 hintvg
.fontFace
= "ui";
143 hintvg
.fontSize
= 18;
144 hintvg
.textAlign
= NVGTextAlign
.H
.Left
;
145 hintvg
.textAlign
= NVGTextAlign
.V
.Baseline
;
146 hintvg
.fillColor(NVGColor
.black
);
147 hintvg
.text(3, 2+hintvg
.textFontAscender
, hinttext
);
152 void calcSizePos (out GxRect rc
) {
153 auto wsz
= hintWindowSize(hinttext
);
154 auto wrc
= getWorkArea();
157 if (nx
+wsz
.width
> wrc
.x1
) nx
= wrc
.x1
-wsz
.width
+1;
158 if (nx
< wrc
.x0
) nx
= wrc
.x0
;
159 if (ny
+wsz
.height
> wrc
.y1
) ny
= wrc
.y1
-wsz
.height
+1;
160 if (ny
< wrc
.y0
) ny
= wrc
.y0
;
164 rc
.width
= wsz
.width
;
165 rc
.height
= wsz
.height
;
169 override void close () { active
= false; super.close(); }
171 this(T
) (int ax
, int ay
, T
[] atext
) if (isAnyCharType
!(T
, true)) {
172 assert(laf
!is null);
174 hinttext
= charArrayToString(atext
);
179 calcSizePos(out wpos
);
181 this.onClosing
= &cbOnClosing
;
182 this.windowResized
= &cbWindowResized
;
183 this.visibleForTheFirstTime
= &cbInit
;
184 this.redrawOpenGlScene
= &cbOnPaint
;
185 //this.handleMouseEvent = &cbOnMouse;
188 auto oldWClass
= sdpyWindowClass
;
189 scope(exit
) sdpyWindowClass
= oldWClass
;
190 sdpyWindowClass
= "BIOACID_HINT_WINDOW";
191 super(wpos
.width
, wpos
.height
, "BioAcidHint", OpenGlOptions
.yes
, Resizability
.fixedSize
, WindowTypes
.undecorated
, WindowFlags
.skipTaskbar|WindowFlags
.alwaysOnTop|WindowFlags
.cannotBeActivated|WindowFlags
.dontAutoShow
);
193 XSetWindowBackground(this.impl
.display
, this.impl
.window
, gxRGB
!(255, 255, 0));
195 // sorry for this hack
196 this.setNetWMWindowType(GetAtom
!("_NET_WM_WINDOW_TYPE_DOCK", true)(this.display
));
197 //this.setNetWMWindowType(GetAtom!("_NET_WM_WINDOW_TYPE_TOOLTIP", true)(this.display));
200 atoms
[0] = GetAtom
!("_NET_WM_STATE_STICKY", true)(this.impl
.display
);
201 atoms
[1] = GetAtom
!("_NET_WM_STATE_SKIP_TASKBAR", true)(this.impl
.display
);
202 atoms
[2] = GetAtom
!("_NET_WM_STATE_SKIP_PAGER", true)(this.impl
.display
);
203 atoms
[3] = GetAtom
!("_NET_WM_STATE_ABOVE", true)(this.impl
.display
);
207 GetAtom
!("_NET_WM_STATE", true)(this.impl
.display
),
210 0 /*PropModeReplace*/,
212 cast(int)atoms
.length
);
214 //if (this.hidden) this.show();
215 XMoveWindow(this.impl
.display
, this.impl
.window
, 6000, 6000);
217 //this.moveResize(wpos.x0, wpos.y0, wpos.width, wpos.height);
218 XMoveResizeWindow(this.impl
.display
, this.impl
.window
, 6000, 6000, wpos
.width
, wpos
.height
);
220 //conwritefln!"CREATED! (%d,%d) [%s]"(wsz.width, wsz.height, hinttext);
221 XMoveResizeWindow(this.impl
.display
, this.impl
.window
, wpos
.x0
, wpos
.y0
, wpos
.width
, wpos
.height
);
225 string
hint () const nothrow @safe @nogc { pragma(inline
, true); return hinttext
; }
227 void hint(T
) (T
[] atext
) if (isAnyCharType
!(T
, true)) {
228 string ht
= charArrayToString(atext
);
229 if (ht
!= hinttext
) {
230 //TODO: check if resize works (it doesn't)
233 calcSizePos(out wpos
);
234 //conwritefln!"NEW: sz=(%d,%d); wsz=(%d,%d)"(this.width, this.height, wpos.width, wpos.height);
235 if (!this.closed
&& active
) {
236 //this.setAsCurrentOpenGlContext(); // make this window active
237 //scope(exit) this.releaseCurrentOpenGlContext();
238 //this.moveResize(wpos.x0, wpos.y0, wpos.width, wpos.height);
239 XMoveResizeWindow(this.impl
.display
, this.impl
.window
, wpos
.x0
, wpos
.y0
, wpos
.width
, wpos
.height
);
245 @property GxPoint
initPos () const nothrow @safe @nogc { pragma(inline
, true); return GxPoint(initX
, initY
); }
249 // ////////////////////////////////////////////////////////////////////////// //
250 __gshared TooltipWindow sdhint
;
251 __gshared Timer hintHideTimer
;
252 __gshared string lastHintText
;
255 // ////////////////////////////////////////////////////////////////////////// //
256 void setHint(T
:const(char)[]) (T
str) {
257 static if (is(T
== typeof(null))) {
260 lastHintText
= TooltipWindow
.charArrayToString(str);
261 if (sdhint
is null || sdhint
.closed || sdhint
.hidden
) return;
262 //sdhint.hint = lastHintText;
263 auto pos
= sdhint
.initPos();
265 sdhint
= new TooltipWindow(pos
.x
, pos
.y
, lastHintText
);
270 // ////////////////////////////////////////////////////////////////////////// //
271 __gshared NotificationAreaIcon trayicon
;
272 __gshared Image
[6] trayimage
; // offline, online, away; same as Status
273 __gshared ContactStatus traystatus
= ContactStatus
.Offline
;
274 __gshared
bool trayIsUnread
= false;
275 __gshared
bool trayBlinkStatus
= true; // true: show kitty
276 __gshared Timer blinkTimer
;
279 void setTrayUnread () {
282 if (blinkTimer
is null) {
283 blinkTimer
= new Timer(800, delegate () {
284 trayBlinkStatus
= !trayBlinkStatus
;
286 trayicon
.icon
= (trayBlinkStatus ? trayimage
[$-1] : trayimage
[traystatus
]);
287 flushGui(); // or it may not redraw itself
291 if (trayBlinkStatus
) {
292 trayicon
.icon
= trayimage
[$-1];
293 flushGui(); // or it may not redraw itself
299 void setTrayStatus (ContactStatus status
) {
300 if (status
!= traystatus || trayIsUnread
) {
301 trayIsUnread
= false;
303 trayicon
.icon
= trayimage
[traystatus
];
304 flushGui(); // or it may not redraw itself
309 void prepareTrayIcon () {
310 static Image
buildTrayIcon (const(uint)[] img
) {
311 auto icon
= new TrueColorImage(16, 16);
312 scope(exit
) { delete icon
.imageData
.bytes
; delete icon
; }
313 foreach (immutable y
; 0..16) {
314 foreach (immutable x
; 0..16) {
316 c
.asUint
= img
[y
*16+x
];
317 if (c
.a
< 0x20) c
= Color
.black
;
318 else if (c
.a
< 0xff) {
320 c
= Color(c
.r
*c
.a
/255, c
.g
*c
.a
/255, c
.b
*c
.a
/255);
322 icon
.setPixel(x
, y
, c
);
325 return Image
.fromMemoryImage(icon
);
328 trayimage
[ContactStatus
.Offline
] = buildTrayIcon(baph16Gray
[]);
329 trayimage
[ContactStatus
.Online
] = buildTrayIcon(baph16Online
[]);
330 trayimage
[ContactStatus
.Away
] = buildTrayIcon(baph16Away
[]);
331 trayimage
[ContactStatus
.Busy
] = buildTrayIcon(baph16Busy
[]);
332 trayimage
[ContactStatus
.Connecting
] = buildTrayIcon(baph16Orange
[]);
333 trayimage
[$-1] = buildTrayIcon(kittyMessage
[]);
334 //traystatus = Contact.Status.Online;
335 trayicon
= new NotificationAreaIcon("BioAcid", trayimage
[traystatus
], (int x
, int y
, MouseButton button
, ModifierState mods
) {
336 //conwritefln!"x=%d; y=%d; button=%u; mods=0x%04x"(x, y, button, mods);
337 if (button
== MouseButton
.middle
) {
339 glconPostDoConCommands
!true();
342 if (button
== MouseButton
.left
) {
343 concmd("win_toggle");
344 glconPostDoConCommands
!true();
348 trayicon
.onEnter
= delegate (int x
, int y
, ModifierState mods
) {
349 if (sdhint
is null || sdhint
.hidden
) {
350 if (sdhint
&& !sdhint
.closed
) sdhint
.close();
352 sdhint
= new TooltipWindow(x
+18, y
+2, lastHintText
);
353 if (hintHideTimer
!is null) hintHideTimer
.destroy();
354 if (sdhint
!is null) {
355 hintHideTimer
= new Timer(2000, delegate () {
356 if (hintHideTimer
!is null) {
357 hintHideTimer
.destroy();
358 hintHideTimer
= null;
359 if (sdhint
!is null && !sdhint
.closed
) {
365 sdhint
= null; // just in case
371 trayicon
.onLeave
= delegate () {