1 /*
2  * Copyright (c) 2008-2009, Thomas Jaeger <ThJaeger@gmail.com>
3  *
4  * Permission to use, copy, modify, and/or distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the above
6  * copyright notice and this permission notice appear in all copies.
7  *
8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
11  * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
13  * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
14  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15  */
16 #include "actions.h"
17 #include "prefs.h"
18 #include "win.h"
19 #include "main.h"
20 #include <glibmm/i18n.h>
21 
22 Glib::RefPtr<Gtk::Builder> widgets;
23 
draw(Cairo::RefPtr<Cairo::Surface> surface,int x,int y,int w,int h,double width,bool inv) const24 void Stroke::draw(Cairo::RefPtr<Cairo::Surface> surface, int x, int y, int w, int h, double width, bool inv) const {
25 	const Cairo::RefPtr<Cairo::Context> ctx = Cairo::Context::create (surface);
26 	x += width; y += width; w -= 2*width; h -= 2*width;
27 	ctx->save();
28 	ctx->translate(x,y);
29 	ctx->scale(w,h);
30 	ctx->set_line_width(2.0*width/(w+h));
31 	if (size()) {
32 		ctx->set_line_cap(Cairo::LINE_CAP_ROUND);
33 		int n = size();
34 		float lambda = sqrt(3)-2.0;
35 		float sum = lambda / (1 - lambda);
36 		std::vector<Point> y(n);
37 		y[0] = points(0) * sum;
38 		for (int j = 0; j < n-1; j++)
39 			y[j+1] = (y[j] + points(j)) * lambda;
40 		std::vector<Point> z(n);
41 		z[n-1] = points(n-1) * (-sum);
42 		for (int j = n-1; j > 0; j--)
43 			z[j-1] = (z[j] - points(j)) * lambda;
44 		for (int j = 0; j < n-1; j++) {
45 			// j -> j+1
46 			if (inv)
47 				ctx->set_source_rgba(time(j), 0.0, 1.0-time(j), 1.0);
48 			else
49 				ctx->set_source_rgba(0.0, time(j), 1.0-time(j), 1.0);
50 			Point p[4];
51 			p[0] = points(j);
52 			p[3] = points(j+1);
53 			p[1] = p[0] + y[j] + z[j];
54 			p[2] = p[3] - y[j+1] - z[j+1];
55 			ctx->move_to(p[0].x, p[0].y);
56 			ctx->curve_to(p[1].x, p[1].y, p[2].x, p[2].y, p[3].x, p[3].y);
57 			ctx->stroke();
58 		}
59 	} else if (!button) {
60 		if (inv)
61 			ctx->set_source_rgba(1.0, 1.0, 0.0, 1.0);
62 		else
63 			ctx->set_source_rgba(0.0, 0.0, 1.0, 1.0);
64 		ctx->move_to(0.33, 0.33);
65 		ctx->line_to(0.67, 0.67);
66 		ctx->move_to(0.33, 0.67);
67 		ctx->line_to(0.67, 0.33);
68 		ctx->stroke();
69 	}
70 	ctx->restore();
71 	Glib::ustring str;
72 	if (modifiers != AnyModifier) {
73 		str = Gtk::AccelGroup::get_label(0, (Gdk::ModifierType)modifiers);
74 		if (str == "")
75 			str = "<>";
76 		else
77 			str = "<" + str.substr(0, str.size()-1) + ">";
78 	}
79 	if (trigger)
80 		str += Glib::ustring::compose("%1\xE2\x86\x92", trigger);
81 	if (timeout)
82 		str += "x";
83 	if (button)
84 		str += Glib::ustring::compose("%1", button);
85 	if (str == "")
86 		return;
87 	if (inv)
88 		ctx->set_source_rgba(0.0, 1.0, 1.0, 0.8);
89 	else
90 		ctx->set_source_rgba(1.0, 0.0, 0.0, 0.8);
91 	float font_size = h*0.5;
92 	Cairo::TextExtents te;
93 	for (;;) {
94 		ctx->set_font_size(font_size);
95 		ctx->get_text_extents(str, te);
96 		if (te.width < w)
97 			break;
98 		font_size *= 0.9;
99 	}
100 	ctx->move_to(x+w/2 - te.x_bearing - te.width/2, y+h/2 - te.y_bearing - te.height/2);
101 	ctx->show_text(str);
102 }
103 
draw_svg(std::string filename) const104 void Stroke::draw_svg(std::string filename) const {
105 	const int S = 32;
106 	const int B = 1;
107 	Cairo::RefPtr<Cairo::SvgSurface> s = Cairo::SvgSurface::create(filename, S, S);
108 	draw(s, B, B, S-2*B, S-2*B);
109 }
110 
111 
draw_(int size,double width,bool inv) const112 Glib::RefPtr<Gdk::Pixbuf> Stroke::draw_(int size, double width, bool inv) const {
113 	Glib::RefPtr<Gdk::Pixbuf> pb = drawEmpty_(size);
114 	int w = size;
115 	int h = size;
116 	int stride = pb->get_rowstride();
117 	guint8 *row = pb->get_pixels();
118 	// This is all pretty messed up
119 	// http://www.archivum.info/gtkmm-list@gnome.org/2007-05/msg00112.html
120 	Cairo::RefPtr<Cairo::ImageSurface> surface = Cairo::ImageSurface::create(row, Cairo::FORMAT_ARGB32, w, h, stride);
121 	draw(surface, 0, 0, pb->get_width(), size, width, inv);
122 	for (int i = 0; i < w; i++) {
123 		guint8 *px = row;
124 		for (int j = 0; j < h; j++) {
125 			guint8 a = px[3];
126 			guint8 r = px[2];
127 			guint8 g = px[1];
128 			guint8 b = px[0];
129 			if (a) {
130 				px[0] = ((((guint)r) << 8) - r) / a;
131 				px[1] = ((((guint)g) << 8) - g) / a;
132 				px[2] = ((((guint)b) << 8) - b) / a;
133 			}
134 			px += 4;
135 		}
136 		row += stride;
137 	}
138 	return pb;
139 }
140 
141 
drawEmpty_(int size)142 Glib::RefPtr<Gdk::Pixbuf> Stroke::drawEmpty_(int size) {
143 	Glib::RefPtr<Gdk::Pixbuf> pb = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB,true,8,size,size);
144 	pb->fill(0x00000000);
145 	return pb;
146 }
147 
148 Source<bool> disabled(false);
149 
150 class MenuCheck : private Base {
151 	IO<bool> &io;
152 	Gtk::CheckMenuItem *check;
notify()153 	virtual void notify() { check->set_active(io.get()); }
on_changed()154 	void on_changed() {
155 		bool b = check->get_active();
156 		if (b == io.get()) return;
157 		io.set(b);
158 	}
159 public:
MenuCheck(IO<bool> & io_,Gtk::CheckMenuItem * check_)160 	MenuCheck(IO<bool> &io_, Gtk::CheckMenuItem *check_) : io(io_), check(check_) {
161 		io.connect(this);
162 		notify();
163 		check->signal_toggled().connect(sigc::mem_fun(*this, &MenuCheck::on_changed));
164 	}
165 };
166 
Win()167 Win::Win() : actions(new Actions), prefs_tab(new Prefs), stats(new Stats) {
168 	show_hide_icon();
169 	prefs.tray_icon.connect(new Notifier(sigc::mem_fun(*this, &Win::show_hide_icon)));
170 	disabled.connect(new Notifier(sigc::mem_fun(*this, &Win::timeout)));
171 
172 	WIDGET(Gtk::CheckMenuItem, menu_disabled, _("D_isabled"), true);
173 	menu.append(menu_disabled);
174 	new MenuCheck(disabled, &menu_disabled);
175 
176 	WIDGET(Gtk::ImageMenuItem, menu_about, Gtk::Stock::ABOUT);
177 	menu.append(menu_about);
178 	menu_about.signal_activate().connect(sigc::mem_fun(*this, &Win::show_about));
179 
180 	WIDGET(Gtk::SeparatorMenuItem, menu_sep);
181 	menu.append(menu_sep);
182 
183 	WIDGET(Gtk::ImageMenuItem, menu_quit, Gtk::Stock::QUIT);
184 	menu.append(menu_quit);
185 	menu_quit.signal_activate().connect(sigc::ptr_fun(&quit));
186 
187 	menu.show_all();
188 
189 	widgets->get_widget("main", win);
190 	RStroke trefoil = Stroke::trefoil();
191 	std::vector<Glib::RefPtr<Gdk::Pixbuf> > icons;
192 	icons.push_back(trefoil->draw(24));
193 	icons.push_back(trefoil->draw(64));
194 	win->set_icon_list(icons);
195 
196 	Gtk::Button* button_hide[4];
197 	widgets->get_widget("button_hide1", button_hide[0]);
198 	widgets->get_widget("button_hide2", button_hide[1]);
199 	widgets->get_widget("button_hide3", button_hide[2]);
200 	widgets->get_widget("button_hide4", button_hide[3]);
201 	for (int i = 0; i < 4; i++)
202 		button_hide[i]->signal_clicked().connect(sigc::mem_fun(win, &Gtk::Window::hide));
203 }
204 
205 extern void icon_warning();
206 
icon_clicked(GtkStatusIcon * status_icon,GdkEventButton * event,gpointer)207 static gboolean icon_clicked(GtkStatusIcon *status_icon, GdkEventButton *event, gpointer) {
208 	if (event->button == 2)
209 		disabled.set(!disabled.get());
210 	return TRUE;
211 }
212 
show_hide_icon()213 void Win::show_hide_icon() {
214 	bool show = prefs.tray_icon.get();
215 	if (show) {
216 		if (icon)
217 			return;
218 		icon = Gtk::StatusIcon::create("");
219 		icon->signal_size_changed().connect(sigc::mem_fun(*this, &Win::on_icon_size_changed));
220 		icon->signal_activate().connect(sigc::mem_fun(*this, &Win::show_hide));
221 		icon->signal_popup_menu().connect(sigc::mem_fun(*this, &Win::show_popup));
222 		if (gtk_major_version > 2 || (gtk_major_version == 2 && gtk_minor_version >= 15))
223 			g_signal_connect(icon->gobj(), "button-release-event", G_CALLBACK(icon_clicked), NULL);
224 	} else {
225 		if (icon)
226 			icon.reset();
227 		icon_warning();
228 	}
229 }
230 
show_popup(guint button,guint32 activate_time)231 void Win::show_popup(guint button, guint32 activate_time) {
232 	if (icon)
233 		icon->popup_menu_at_position(menu, button, activate_time);
234 }
235 
236 extern const char *version_string;
show_about()237 void Win::show_about() {
238 	Gtk::AboutDialog *about;
239 	widgets->get_widget("aboutdialog", about);
240 	about->set_logo(Stroke::trefoil()->draw(96, 4.0));
241 	about->set_version(version_string);
242 	about->set_program_name("easystroke\n");
243 	about->show();
244 	about->run();
245 	about->hide();
246 }
247 
show_hide()248 void Win::show_hide() {
249 	if (win->get_mapped())
250 		win->hide();
251 	else
252 		win->show();
253 }
254 
show()255 void Win::show() {
256 	win->show();
257 }
258 
hide()259 void Win::hide() {
260 	win->hide();
261 }
262 
on_icon_size_changed(int size)263 bool Win::on_icon_size_changed(int size) {
264 	icon_pb[0] = Stroke::trefoil()->draw(size);
265 	icon_pb[1] = Stroke::trefoil()->draw(size);
266 	icon_pb[1]->saturate_and_pixelate(icon_pb[1], 0.0, true);
267 	if (icon)
268 		icon->set(icon_pb[disabled.get() ? 1 : 0]);
269 	return true;
270 }
271 
timeout()272 void Win::timeout() {
273 	if (icon)
274 		icon->set(icon_pb[disabled.get() ? 1 : 0]);
275 }
276 
set_icon(RStroke stroke,bool invert)277 void Win::set_icon(RStroke stroke, bool invert) {
278 	if (!icon)
279 		return;
280 	icon->set(stroke->draw(icon->get_size(), 2.0, invert));
281 	set_timeout(10000);
282 }
283 
error_dialog(const Glib::ustring & text)284 void error_dialog(const Glib::ustring &text) {
285 	Gtk::MessageDialog dialog(win->get_window(), text, false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
286 	dialog.show();
287 	dialog.run();
288 }
289 
app_name_hr(Glib::ustring src)290 Glib::ustring app_name_hr(Glib::ustring src) {
291 	return src == "" ? _("<unnamed>") : src;
292 }
293