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