diff --git a/Makefile b/Makefile index 9a863152..0bbd1517 100644 --- a/Makefile +++ b/Makefile @@ -21,6 +21,7 @@ LOCALEDIR= $(PREFIX)/share/locale DFLAGS = OFLAGS = -Os AOFLAGS = -O3 +STROKEFLAGS = -Wall -std=c99 $(DFLAGS) CXXFLAGS = -Wall $(DFLAGS) -DLOCALEDIR=\"$(LOCALEDIR)\" `pkg-config gtkmm-2.4 dbus-glib-1 --cflags` LDFLAGS = $(DFLAGS) @@ -33,7 +34,7 @@ MANPAGE = easystroke.1 CCFILES = $(wildcard *.cc) HFILES = $(wildcard *.h) -OFILES = $(patsubst %.cc,%.o,$(CCFILES)) gui.o desktop.o version.o +OFILES = $(patsubst %.cc,%.o,$(CCFILES)) stroke.o gui.o desktop.o version.o POFILES = $(wildcard po/*.po) MOFILES = $(patsubst po/%.po,po/%/LC_MESSAGES/easystroke.mo,$(POFILES)) MODIRS = $(patsubst po/%.po,po/%,$(POFILES)) @@ -61,8 +62,8 @@ include $(DEPFILES) $(BINARY): $(OFILES) $(CXX) $(LDFLAGS) -o $@ $(OFILES) $(LIBS) -stroke.o: stroke.cc - $(CXX) $(CXXFLAGS) $(AOFLAGS) -MT $@ -MMD -MP -MF $*.Po -o $@ -c $< +stroke.o: stroke.c + $(CC) $(STROKEFLAGS) $(AOFLAGS) -MT $@ -MMD -MP -MF $*.Po -o $@ -c $< %.o: %.cc $(CXX) $(CXXFLAGS) $(OFLAGS) -MT $@ -MMD -MP -MF $*.Po -o $@ -c $< diff --git a/actiondb.cc b/actiondb.cc index 6e979d9c..edd88729 100644 --- a/actiondb.cc +++ b/actiondb.cc @@ -303,18 +303,18 @@ RAction ActionListDiff::handle(RStroke s, Ranking &r) const { if (!s) return RAction(); r.stroke = s; - r.score = -1; + r.score = 0.0; boost::shared_ptr > strokes = get_strokes(); for (std::map::const_iterator i = strokes->begin(); i!=strokes->end(); i++) { for (StrokeSet::iterator j = i->second.begin(); j!=i->second.end(); j++) { double score; - bool match = Stroke::compare(s, *j, score); - if (score < 0.25) + int match = Stroke::compare(s, *j, score); + if (match < 0) continue; RStrokeInfo si = get_info(i->first); r.r.insert(pair > (score, pair(si->name, *j))); - if (score >= r.score) { + if (score > r.score) { r.score = score; if (match) { r.name = si->name; @@ -348,8 +348,8 @@ void ActionListDiff::handle_advanced(RStroke s, std::map &as, continue; s->button = b; double score; - bool match = Stroke::compare(s, *j, score); - if (score < 0.25) + int match = Stroke::compare(s, *j, score); + if (match < 0) continue; Ranking *r; if (b == b1) @@ -365,7 +365,7 @@ void ActionListDiff::handle_advanced(RStroke s, std::map &as, RStrokeInfo si = get_info(i->first); r->r.insert(pair > (score, pair(si->name, *j))); - if (score >= r->score) { + if (score > r->score) { r->score = score; if (match) { r->name = si->name; diff --git a/actiondb.h b/actiondb.h index 2353a3a2..40492fce 100644 --- a/actiondb.h +++ b/actiondb.h @@ -23,7 +23,7 @@ #include #include -#include "stroke.h" +#include "gesture.h" #include "prefdb.h" class Action; diff --git a/doc/Makefile b/doc/Makefile deleted file mode 100644 index 50c7b521..00000000 --- a/doc/Makefile +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright (c) 2008, Thomas Jaeger -# -# Permission to use, copy, modify, and/or distribute this software for any -# purpose with or without fee is hereby granted, provided that the above -# copyright notice and this permission notice appear in all copies. -# -# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY -# SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION -# OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN -# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -all: algo.pdf - -algo.pdf: algo.tex - pdflatex algo.tex - -clean: - rm algo.aux algo.log algo.pdf diff --git a/doc/algo.tex b/doc/algo.tex deleted file mode 100644 index adeb64e5..00000000 --- a/doc/algo.tex +++ /dev/null @@ -1,42 +0,0 @@ -% Copyright (c) 2008, Thomas Jaeger -% -% Permission to use, copy, modify, and/or distribute this software for any -% purpose with or without fee is hereby granted, provided that the above -% copyright notice and this permission notice appear in all copies. -% -% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY -% SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION -% OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN -% CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -\documentclass{article} -\usepackage{amsfonts} -\newcommand{\R}{\mathbb{R}} -\begin{document} -A stroke $z: [0,1] \rightarrow \R^2$ is represented by a sequence of line -segments in such a way that $z'(t)$ is equal to the same constant almost -everywhere. -\begin{enumerate} -\item -Define -\[ \tilde{d}(z,w) = \min_{a \in \R^2, k \in \R} \int_0^1 \|z(t) - k w(t) - a\|^2 \, dt \] -then the distance of $z$ and $w$ is given by -\[ d(z,w) = \pm \sqrt{1-\frac{\tilde{d}(z,w)}{\tilde{d}(z,0)}} \] -where the sign depends on whether the minimum is attained for a positive or -negative $k$. Clearly, $|d(z,w)| \leq 1$. It turns out that $d$ is symmetric, -but there is no ``triangle inequality'' that I am aware of. - -\item -More generally, for $0 \leq p \leq 1$, we define -\[ d^k(z, w) = \min_{a \in \R^2} \int_0^1 \|z(t) - k w(t) - a\|^2 \, dt\] -\[ \tilde{d}^k(z, w) = \int_0^1 \|z'(t) - k w'(t)\|^2 \, dt\] -\[ d_p(z, w) = \pm \sqrt{1 - \min_{k \in \R}\left( (1-p)\frac{d^k(z,w)}{d^k(z,0)} + p\frac{\tilde{d}^k(z,w)}{\tilde{d}^k(z,0)} \right)} \] -It is easy to see that $|d_p(z,w)| \leq 1$ and $d_0(z,w) = d(z,w)$. -Unfortunately $d_p$ is in general not symmetric anymore, but usually -$d_p(z,w)$ and $d_p(w,z)$ are close. - -\end{enumerate} - -\end{document} diff --git a/gesture.cc b/gesture.cc new file mode 100644 index 00000000..b224eb35 --- /dev/null +++ b/gesture.cc @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2008-2009, Thomas Jaeger + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include "gesture.h" +#include "prefdb.h" + +void update_triple(RTriple e, float x, float y, Time t) { + e->x = x; + e->y = y; + e->t = t; +} + +RTriple create_triple(float x, float y, Time t) { + RTriple e(new Triple); + update_triple(e, x, y, t); + return e; +} + +Stroke::Stroke(PreStroke &ps, int trigger_, int button_, bool timeout_) : button(button_), timeout(timeout_) { + trigger = (trigger_ == get_default_button()) ? 0 : trigger_; + + if (ps.valid()) { + stroke_t *s = stroke_alloc(ps.size()); + for (std::vector::iterator i = ps.begin(); i != ps.end(); ++i) + stroke_add_point(s, (*i)->x, (*i)->y); + stroke_finish(s); + stroke.reset(s, &stroke_free); + } +} + +int Stroke::compare(RStroke a, RStroke b, double &score) { + score = 0.0; + if (!a || !b) + return -1; + if (!a->timeout != !b->timeout) + return -1; + if (a->button != b->button) + return -1; + if (a->trigger != b->trigger) { + if (a->trigger && b->trigger) + return -1; + if (a->trigger + b->trigger != get_default_button()) + return -1; + } + if (!a->stroke || !b->stroke) { + if (!a->stroke && !b->stroke) { + score = 1.0; + return 1; + } + else + return -1; + } + double cost = stroke_compare(a->stroke.get(), b->stroke.get(), NULL, NULL); + if (cost >= stroke_infinity) + return -1; + score = MAX(1.0 - 2.5*cost, 0.0); + if (a->timeout) + return score > 0.85; + else + return score > 0.7; +} + +Glib::RefPtr Stroke::draw(int size, double width) const { + if (size != STROKE_SIZE || (width != 2.0 && width != 4.0)) + return draw_(size, width); + int i = width == 2.0; + if (pb[i]) + return pb[i]; + pb[i] = draw_(size, width); + return pb[i]; +} + +Glib::RefPtr Stroke::pbEmpty; + +Glib::RefPtr Stroke::drawEmpty(int size) { + if (size != STROKE_SIZE) + return drawEmpty_(size); + if (pbEmpty) + return pbEmpty; + pbEmpty = drawEmpty_(size); + return pbEmpty; +} + + +RStroke Stroke::trefoil() { + PreStroke s; + const int n = 40; + for (int i = 0; i<=n; i++) { + double phi = M_PI*(-4.0*i/n)-2.7; + double r = exp(1.0 + sin(6.0*M_PI*i/n)) + 2.0; + s.add(create_triple(r*cos(phi), r*sin(phi), i)); + } + return Stroke::create(s, 0, 0, false); +} diff --git a/gesture.h b/gesture.h new file mode 100644 index 00000000..c1f4e10b --- /dev/null +++ b/gesture.h @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2008-2009, Thomas Jaeger + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef __GESTURE_H__ +#define __GESTURE_H__ + +#include "stroke.h" +#include +#include +#include +#include +#include +#include + +#include // Time + +#define STROKE_SIZE 64 + +class Stroke; +class PreStroke; + +typedef boost::shared_ptr RStroke; +typedef boost::shared_ptr RPreStroke; + +int get_default_button(); + +struct Triple { + float x; + float y; + Time t; +}; +typedef boost::shared_ptr RTriple; +void update_triple(RTriple e, float x, float y, Time t); +RTriple create_triple(float x, float y, Time t); + +class PreStroke; +class Stroke { + friend class PreStroke; + friend class boost::serialization::access; + friend class Stats; +public: + struct Point { + double x; + double y; + Point operator+(const Point &p) { + Point sum = { x + p.x, y + p.y }; + return sum; + } + Point operator-(const Point &p) { + Point sum = { x - p.x, y - p.y }; + return sum; + } + Point operator*(const double a) { + Point product = { x * a, y * a }; + return product; + } + template void serialize(Archive & ar, const unsigned int version) { + ar & x; ar & y; + if (version == 0) { + double time; + ar & time; + } + } + }; + +private: + Stroke(PreStroke &s, int trigger_, int button_, bool timeout_); + + Glib::RefPtr draw_(int size, double width = 2.0) const; + mutable Glib::RefPtr pb[2]; + + static Glib::RefPtr drawEmpty_(int); + static Glib::RefPtr pbEmpty; + + BOOST_SERIALIZATION_SPLIT_MEMBER() + template void save(Archive & ar, const unsigned int version) const { + std::vector ps; + for (unsigned int i = 0; i < size(); i++) + ps.push_back(points(i)); + ar & ps; + ar & button; + ar & trigger; + ar & timeout; + } + template void load(Archive & ar, const unsigned int version) { + std::vector ps; + ar & ps; + if (ps.size()) { + stroke_t *s = stroke_alloc(ps.size()); + for (std::vector::iterator i = ps.begin(); i != ps.end(); ++i) + stroke_add_point(s, i->x, i->y); + stroke_finish(s); + stroke.reset(s, &stroke_free); + } + if (version == 0) return; + ar & button; + if (version >= 2) + ar & trigger; + if (version < 4 && (!button || trigger == get_default_button())) + trigger = 0; + if (version < 3) + return; + ar & timeout; + } + +public: + int trigger; + int button; + bool timeout; + boost::shared_ptr stroke; + + Stroke() : trigger(0), button(0), timeout(false) {} + static RStroke create(PreStroke &s, int trigger_, int button_, bool timeout_) { + return RStroke(new Stroke(s, trigger_, button_, timeout_)); + } + Glib::RefPtr draw(int size, double width = 2.0) const; + void draw(Cairo::RefPtr surface, int x, int y, int w, int h, double width = 2.0) const; + void draw_svg(std::string filename) const; + bool show_icon(); + + static RStroke trefoil(); + static int compare(RStroke, RStroke, double &); + static Glib::RefPtr drawEmpty(int); + static Glib::RefPtr drawDebug(RStroke, RStroke, int); + + unsigned int size() const { return stroke ? stroke_get_size(stroke.get()) : 0; } + bool trivial() const { return size() == 0 && button == 0; } + Point points(int n) const { Point p; stroke_get_point(stroke.get(), n, &p.x, &p.y); return p; } + double time(int n) const { return stroke_get_time(stroke.get(), n); } + bool is_timeout() const { return timeout; } +}; +BOOST_CLASS_VERSION(Stroke, 4) +BOOST_CLASS_VERSION(Stroke::Point, 1) + +class PreStroke : public std::vector { +public: + static RPreStroke create() { return RPreStroke(new PreStroke()); } + void add(RTriple p) { push_back(p); } + bool valid() const { return size() > 2; } +}; +#endif diff --git a/gui.glade b/gui.glade index 7e8367f0..45445361 100644 --- a/gui.glade +++ b/gui.glade @@ -986,80 +986,6 @@ than this many pixels away from the original position 4 - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 6 - - - True - Algorithm - - - False - False - 0 - - - - - True - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - none - - - False - False - 1 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Shape - - - False - False - 2 - - - - - 150 - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - adjustment2 - 2 - - - False - False - 3 - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - Direction - - - False - False - 4 - - - - - 5 - - diff --git a/main.cc b/main.cc index 33f7131d..2ef9f212 100644 --- a/main.cc +++ b/main.cc @@ -46,7 +46,7 @@ extern Source disabled; bool experimental = false; int verbosity = 0; -const char *versions[] = { "-0.4.0", "", NULL }; +const char *versions[] = { "-0.4.1", "-0.4.0", "", NULL }; Source current_window(None); std::string config_dir; Win *win = NULL; @@ -1642,7 +1642,7 @@ void Main::handle_event(XEvent &ev) { return; case MotionNotify: - if (verbosity >= 4) + if (verbosity >= 5) printf("Motion: (%d, %d)\n", ev.xmotion.x, ev.xmotion.y); if (!grabber->xinput) H->motion(create_triple(ev.xmotion.x, ev.xmotion.y, ev.xmotion.time)); @@ -1751,7 +1751,7 @@ void Main::handle_event(XEvent &ev) { if (grabber->is_event(ev.type, Grabber::MOTION)) { XDeviceMotionEvent* mev = (XDeviceMotionEvent *)&ev; - if (verbosity >= 4) + if (verbosity >= 5) printf("Motion (Xi): (%d, %d, %d, %d, %d)\n", mev->x, mev->y, mev->axis_data[0], mev->axis_data[1], mev->axis_data[2]); if (!current_dev || current_dev->dev->device_id != mev->deviceid) diff --git a/prefdb.cc b/prefdb.cc index 070e4b40..a497cfc0 100644 --- a/prefdb.cc +++ b/prefdb.cc @@ -28,7 +28,6 @@ #include #include -const double default_p = 0.5; const ButtonInfo default_button(Button2); const int default_radius = 16; const int default_pressure_threshold = 192; @@ -36,7 +35,6 @@ const int default_pressure_threshold = 192; PrefDB::PrefDB() : TimeoutWatcher(5000), good_state(true), - p(default_p), button(default_button), trace(TraceDefault), advanced_ignore(false), @@ -65,7 +63,10 @@ template void PrefDB::serialize(Archive & ar, const unsigned int for (std::set::iterator i = old.begin(); i != old.end(); i++) exceptions.unsafe_ref()[*i] = RButtonInfo(); } else ar & exceptions.unsafe_ref(); - ar & p.unsafe_ref(); + if (version < 14) { + double p = 0.5; + ar & p; + } ar & button.unsafe_ref(); if (version < 2) { bool help; @@ -221,7 +222,6 @@ void PrefDB::init() { } new TimeoutProfile(prefs.timeout_profile); watch(exceptions); - watch(p); watch(button); watch(trace); watch(advanced_ignore); diff --git a/prefdb.h b/prefdb.h index 0ca34b92..14c74ad5 100644 --- a/prefdb.h +++ b/prefdb.h @@ -91,7 +91,6 @@ struct RGBA { BOOST_SERIALIZATION_SPLIT_MEMBER() }; -extern const double default_p; extern const ButtonInfo default_button; extern const int default_radius; extern const int default_pressure_threshold; @@ -104,7 +103,6 @@ class PrefDB : public TimeoutWatcher { PrefDB(); Source > exceptions; - Source p; Source button; Source trace; Source advanced_ignore; @@ -131,7 +129,7 @@ class PrefDB : public TimeoutWatcher { virtual void timeout(); }; -BOOST_CLASS_VERSION(PrefDB, 13) +BOOST_CLASS_VERSION(PrefDB, 14) extern PrefDB prefs; #endif diff --git a/prefs.cc b/prefs.cc index 5060dd29..59a70aa3 100644 --- a/prefs.cc +++ b/prefs.cc @@ -274,10 +274,9 @@ Prefs::Prefs() { new Check(prefs.timeout_gestures, "check_timeout_gestures"); - Gtk::Button *bbutton, *add_exception, *remove_exception, *button_default_p, *add_extra, *edit_extra, *remove_extra; + Gtk::Button *bbutton, *add_exception, *remove_exception, *add_extra, *edit_extra, *remove_extra; widgets->get_widget("button_add_exception", add_exception); widgets->get_widget("button_button", bbutton); - widgets->get_widget("button_default_p", button_default_p); widgets->get_widget("button_remove_exception", remove_exception); widgets->get_widget("label_button", blabel); widgets->get_widget("button_add_extra", add_extra); @@ -286,7 +285,6 @@ Prefs::Prefs() { widgets->get_widget("treeview_exceptions", tv); widgets->get_widget("treeview_devices", dtv); widgets->get_widget("treeview_extra", etv); - widgets->get_widget("scale_p", scale_p); new Sensitive(xinput_v, "check_timing_workaround"); new Sensitive(xinput_v, "check_ignore_grab"); @@ -333,15 +331,8 @@ Prefs::Prefs() { etv->append_column(_("Button"), ecs.str); update_extra_buttons(); - double p = prefs.p.get(); - scale_p->set_value(p); - scale_p->signal_value_changed().connect(sigc::mem_fun(*this, &Prefs::on_p_changed)); - button_default_p->signal_clicked().connect(sigc::mem_fun(*this, &Prefs::on_p_default)); - if (!experimental) { Gtk::HBox *hbox; - widgets->get_widget("hbox_algo", hbox); - hbox->hide(); widgets->get_widget("hbox_timeout", hbox); hbox->hide(); } @@ -572,14 +563,6 @@ void Prefs::on_select_button() { set_button_label(); } -void Prefs::on_p_changed() { - prefs.p.set(scale_p->get_value()); -} - -void Prefs::on_p_default() { - scale_p->set_value(default_p); -} - bool Prefs::select_row(const Gtk::TreeModel::Path& path, const Gtk::TreeModel::iterator& iter, std::string name) { if ((std::string)(*iter)[cols.app] == name) { tv->set_cursor(path); diff --git a/prefs.h b/prefs.h index acb9cd2e..0991550b 100644 --- a/prefs.h +++ b/prefs.h @@ -33,8 +33,6 @@ class Prefs { void on_add_extra(); void on_edit_extra(); void on_remove_extra(); - void on_p_changed(); - void on_p_default(); void on_select_button(); void on_button_editing_started(Gtk::CellEditable* editable, const Glib::ustring& path); void on_device_toggled(const Gtk::TreeModel::Path& path, const Gtk::TreeModel::iterator& iter); @@ -70,7 +68,6 @@ class Prefs { Gtk::TreeView *etv; Glib::RefPtr etm; - Gtk::HScale* scale_p; Gtk::SpinButton *spin_radius; Gtk::Label* blabel; bool ignore_device_toggled; diff --git a/stats.cc b/stats.cc index 7dc34216..2bdb304f 100644 --- a/stats.cc +++ b/stats.cc @@ -16,6 +16,7 @@ #include "stats.h" #include "win.h" #include "actiondb.h" +#include "main.h" #include #include @@ -36,6 +37,8 @@ Stats::Stats() { ranking_view->set_model(Gtk::ListStore::create(cols)); ranking_view->append_column(_("Stroke"), cols.stroke); + if (verbosity >= 4) + ranking_view->append_column("Debug", cols.debug); ranking_view->append_column(_("Name"), cols.name); ranking_view->append_column(_("Score"), cols.score); } @@ -124,6 +127,60 @@ Glib::ustring format_float(float x) { return Glib::ustring::format(std::fixed, std::setprecision(2), x); } +Glib::RefPtr Stroke::drawDebug(RStroke a, RStroke b, int size) { + // TODO: This is copy'n'paste from win.cc + Glib::RefPtr pb = drawEmpty_(size); + if (!a || !b || !a->stroke || !b->stroke) + return pb; + int w = size; + int h = size; + int stride = pb->get_rowstride(); + guint8 *row = pb->get_pixels(); + // This is all pretty messed up + // http://www.archivum.info/gtkmm-list@gnome.org/2007-05/msg00112.html + Cairo::RefPtr surface = Cairo::ImageSurface::create(row, Cairo::FORMAT_ARGB32, w, h, stride); + const Cairo::RefPtr ctx = Cairo::Context::create(surface); + + for (unsigned int s = 0; s+1 < a->size(); s++) + for (unsigned int t = 0; t+1 < b->size(); t++) { + double col = 1.0 - stroke_angle_difference(a->stroke.get(), b->stroke.get(), s, t); + ctx->set_source_rgba(col,col,col,1.0); + ctx->rectangle(a->time(s)*size, (1.0-b->time(t+1))*size, + (a->time(s+1)-a->time(s))*size, (b->time(t+1)-b->time(t))*size); + ctx->fill(); + } + int path_x[a->size() + b->size()]; + int path_y[a->size() + b->size()]; + stroke_compare(a->stroke.get(), b->stroke.get(), path_x, path_y); + ctx->set_source_rgba(1,0,0,1); + ctx->set_line_width(2); + ctx->move_to(size, 0); + for (int i = 0;; i++) { + ctx->line_to(a->time(path_x[i])*size, (1.0-b->time(path_y[i]))*size); + if (!path_x[i] && !path_y[i]) + break; + } + ctx->stroke(); + + for (int i = 0; i < w; i++) { + guint8 *px = row; + for (int j = 0; j < h; j++) { + guint8 a = px[3]; + guint8 r = px[2]; + guint8 g = px[1]; + guint8 b = px[0]; + if (a) { + px[0] = ((((guint)r) << 8) - r) / a; + px[1] = ((((guint)g) << 8) - g) / a; + px[2] = ((((guint)b) << 8) - b) / a; + } + px += 4; + } + row += stride; + } + return pb; +} + bool Stats::on_stroke(Ranking *r) { Gtk::TreeModel::Row row = *(recent_store->prepend()); row[cols.stroke] = r->stroke->draw(STROKE_SIZE); @@ -146,6 +203,8 @@ bool Stats::on_stroke(Ranking *r) { for (std::multimap >::iterator i = r->r.begin(); i != r->r.end(); i++) { Gtk::TreeModel::Row row2 = *(ranking_store->prepend()); row2[cols.stroke] = i->second.second->draw(STROKE_SIZE); + if (verbosity >= 4) + row2[cols.debug] = Stroke::drawDebug(r->stroke, i->second.second, STROKE_SIZE); row2[cols.name] = i->second.first; row2[cols.score] = format_float(i->first * 100) + "%"; } @@ -153,59 +212,9 @@ bool Stats::on_stroke(Ranking *r) { return false; } -#define GRAPH 0 -#define TRIANGLE 0 - -#if TRIANGLE -double abs(double x) { - return x > 0 ? x : (-x); -} - -void test_triangle_inequality() { - int bad = 0; - int good = 0; - double worst = 0; - std::string worsti, worstj, worstk; - Ref ref(actions()); - for (StrokeIterator i = ref->strokes_begin(); i; i++) { - for (StrokeIterator j = ref->strokes_begin(); j; j++) { - if (i.stroke() == j.stroke()) - continue; - for (StrokeIterator k = ref->strokes_begin(); k; k++) { - if (i.stroke() == k.stroke()) - continue; - if (j.stroke() == k.stroke()) - continue; - double scoreij = Stroke::compare(i.stroke(), j.stroke()); - double scorejk = Stroke::compare(j.stroke(), k.stroke()); - double scoreik = Stroke::compare(i.stroke(), k.stroke()); -// double score = abs(scoreij*scorejk/scoreik); - double score = 1-abs(scoreik) - (1-abs(scoreij) + 1-abs(scorejk)); - if (score > worst) { - worst = score; - worsti = i.name(); - worstj = j.name(); - worstk = k.name(); - } - if (1-abs(scoreij) + 1-abs(scorejk) < 1-abs(scoreik)) -// if (abs(scoreij * scorejk) > abs(scoreik)) - bad++; - else - good++; - } - } - } - printf("good: %d, bad: %d\n", good, bad); - std::cout << "worst: " << worsti << ", " << worstj << ", " << worstk << " (" << worst << ")" << std::endl; -} -#endif - - void Stats::on_pdf() { -#if TRIANGLE - test_triangle_inequality(); -#endif - { + if (verbosity >= 1) + show_us("Creating table: "); const int S = 32; const int B = 1; std::list strokes; @@ -229,7 +238,9 @@ void Stats::on_pdf() { int l = 1; for (std::list::iterator j = strokes.begin(); j != strokes.end(); j++, l++) { double score; - bool match = Stroke::compare(*i, *j, score); + int match = Stroke::compare(*i, *j, score); + if (match < 0) + continue; if (match) { ctx->save(); ctx->set_source_rgba(0,0,1,score-0.6); @@ -237,25 +248,15 @@ void Stats::on_pdf() { ctx->fill(); ctx->restore(); } - if (score < -1.5) - continue; Glib::ustring str = format_float(score); Cairo::TextExtents te; ctx->get_text_extents(str, te); ctx->move_to(l*S+S/2 - te.x_bearing - te.width/2, k*S+S/2 - te.y_bearing - te.height/2); ctx->show_text(str); -#if GRAPH - score = Stroke::compare(i.stroke(), j.stroke(), 0); - ctx->move_to(l*S, k*S + (1-score)*S/2); - for (int p = 1; p!= 16; p++) { - score = Stroke::compare(i.stroke(), j.stroke(), p/15.0); - ctx->line_to(l*S+p*S/15.0, k*S + (1-score)*S/2); - } - ctx->stroke(); -#endif } } - } + if (verbosity >= 1) + show_us("Table completed: "); if (!fork()) { execlp("evince", "evince", "/tmp/strokes.pdf", NULL); exit(EXIT_FAILURE); diff --git a/stats.h b/stats.h index c4f7015e..b24ea274 100644 --- a/stats.h +++ b/stats.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, Thomas Jaeger + * Copyright (c) 2008-2009, Thomas Jaeger * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -29,9 +29,10 @@ class Stats { class ModelColumns : public Gtk::TreeModel::ColumnRecord { public: - ModelColumns() { add(stroke); add(name); add(score); add(child); } + ModelColumns() { add(stroke); add(debug); add(name); add(score); add(child); } Gtk::TreeModelColumn > stroke; + Gtk::TreeModelColumn > debug; Gtk::TreeModelColumn name; Gtk::TreeModelColumn score; Gtk::TreeModelColumn > child; diff --git a/stroke.c b/stroke.c new file mode 100644 index 00000000..a62b3bc9 --- /dev/null +++ b/stroke.c @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2009, Thomas Jaeger + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define _GNU_SOURCE + +#include "stroke.h" +#include +#include +#include +#include + +const double stroke_infinity = 0.2; +#define EPS 0.000001 + +struct point { + double x; + double y; + double t; + double dt; + double alpha; +}; + +struct _stroke_t { + int n; + int capacity; + struct point *p; +}; + +stroke_t *stroke_alloc(int n) { + assert(n > 0); + stroke_t *s = malloc(sizeof(stroke_t)); + s->n = 0; + s->capacity = n; + s->p = calloc(n, sizeof(struct point)); + return s; +} + +void stroke_add_point(stroke_t *s, double x, double y) { + assert(s->capacity > s->n); + s->p[s->n].x = x; + s->p[s->n].y = y; + s->n++; +} + +inline static double angle_difference(double alpha, double beta) { + // return 1.0 - cos((alpha - beta) * M_PI); + double d = alpha - beta; + if (d < -1.0) + d += 2.0; + else if (d > 1.0) + d -= 2.0; + return d; +} + +void stroke_finish(stroke_t *s) { + assert(s->capacity > 0); + s->capacity = -1; + + int n = s->n - 1; + double total = 0.0; + s->p[0].t = 0.0; + for (int i = 0; i < n; i++) { + total += hypot(s->p[i+1].x - s->p[i].x, s->p[i+1].y - s->p[i].y); + s->p[i+1].t = total; + } + for (int i = 0; i <= n; i++) + s->p[i].t /= total; + double minX = s->p[0].x, minY = s->p[0].y, maxX = minX, maxY = minY; + for (int i = 1; i <= n; i++) { + if (s->p[i].x < minX) minX = s->p[i].x; + if (s->p[i].x > maxX) maxX = s->p[i].x; + if (s->p[i].y < minY) minY = s->p[i].y; + if (s->p[i].y > maxY) maxY = s->p[i].y; + } + double scaleX = maxX - minX; + double scaleY = maxY - minY; + double scale = (scaleX > scaleY) ? scaleX : scaleY; + if (scale < 0.001) scale = 1; + for (int i = 0; i <= n; i++) { + s->p[i].x = (s->p[i].x-(minX+maxX)/2)/scale + 0.5; + s->p[i].y = (s->p[i].y-(minY+maxY)/2)/scale + 0.5; + } + + for (int i = 0; i < n; i++) { + s->p[i].dt = s->p[i+1].t - s->p[i].t; + s->p[i].alpha = atan2(s->p[i+1].y - s->p[i].y, s->p[i+1].x - s->p[i].x)/M_PI; + } + +} + +void stroke_free(stroke_t *s) { + if (s) + free(s->p); + free(s); +} + +int stroke_get_size(const stroke_t *s) { return s->n; } + +void stroke_get_point(const stroke_t *s, int n, double *x, double *y) { + assert(n < s->n); + if (x) + *x = s->p[n].x; + if (y) + *y = s->p[n].y; +} + +double stroke_get_time(const stroke_t *s, int n) { + assert(n < s->n); + return s->p[n].t; +} + +double stroke_get_angle(const stroke_t *s, int n) { + assert(n+1 < s->n); + return s->p[n].alpha; +} + +inline static double sqr(double x) { return x*x; } + +double stroke_angle_difference(const stroke_t *a, const stroke_t *b, int i, int j) { + return fabs(angle_difference(stroke_get_angle(a, i), stroke_get_angle(b, j))); +} + +double stroke_compare(const stroke_t *a, const stroke_t *b, int *path_x, int *path_y) { + int m = a->n - 1; + int n = b->n - 1; + + double dist[m+1][n+1]; + int prev_x[m+1][n+1]; + int prev_y[m+1][n+1]; + for (int i = 0; i < m; i++) + for (int j = 0; j < n; j++) + dist[i][j] = stroke_infinity; + dist[m][n] = stroke_infinity; + dist[0][0] = 0.0; + + for (int x = 0; x < m; x++) { + for (int y = 0; y < n; y++) { + if (dist[x][y] >= stroke_infinity) + continue; + double tx = a->p[x].t; + double ty = b->p[y].t; + int max_x = x; + int max_y = y; + int k = 0; + + inline void step(int x2, int y2) { + double dtx = a->p[x2].t - tx; + double dty = b->p[y2].t - ty; + if (dtx >= dty * 2.2 || dty >= dtx * 2.2 || dtx < EPS || dty < EPS) + return; + k++; + + inline double ad(int i, int j) {return sqr(angle_difference(a->p[i].alpha, b->p[j].alpha));} + double d = (a->p[x].dt + b->p[y].dt) * ad(x,y); + for (int x_ = x+1; x_ < x2; x_++) + d += a->p[x_].dt * ad(x_,y); + for (int y_ = y+1; y_ < y2; y_++) + d += b->p[y_].dt * ad(x, y_); + double new_dist = dist[x][y] + d; + if (new_dist != new_dist) abort(); + + if (new_dist >= dist[x2][y2]) + return; + + prev_x[x2][y2] = x; + prev_y[x2][y2] = y; + dist[x2][y2] = new_dist; + } + + while (k < 4) { + if (a->p[max_x+1].t - tx > b->p[max_y+1].t - ty) { + max_y++; + if (max_y == n) { + step(m, n); + break; + } + for (int x2 = x+1; x2 <= max_x; x2++) + step(x2, max_y); + } else { + max_x++; + if (max_x == m) { + step(m, n); + break; + } + for (int y2 = y+1; y2 <= max_y; y2++) + step(max_x, y2); + } + } + } + } + double cost = dist[m][n]; + if (path_x && path_y) { + if (cost < stroke_infinity) { + int x = m; + int y = n; + int k = 0; + while (x || y) { + int old_x = x; + x = prev_x[x][y]; + y = prev_y[old_x][y]; + path_x[k] = x; + path_y[k] = y; + k++; + } + } else { + path_x[0] = 0; + path_y[0] = 0; + } + } + return cost; +} diff --git a/stroke.cc b/stroke.cc deleted file mode 100644 index 78cf04c6..00000000 --- a/stroke.cc +++ /dev/null @@ -1,396 +0,0 @@ -/* - * Copyright (c) 2008-2009, Thomas Jaeger - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY - * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION - * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN - * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ -#include "stroke.h" -#include "prefdb.h" -#include -#include -#include - -#define eps 0.000001 - -using namespace std; - -inline bool close(double x, double y) { - double diff = x - y; - if (diff < 0) - diff = -diff; - return diff < eps; -} - -inline double sqr(double x) { return x*x; }; - -void update_triple(RTriple e, float x, float y, Time t) { - e->x = x; - e->y = y; - e->t = t; -} - -RTriple create_triple(float x, float y, Time t) { - RTriple e(new Triple); - update_triple(e, x, y, t); - return e; -} - -struct f : public std::unary_function { - Stroke::Point operator()(RTriple e) { - Stroke::Point p = { e->x, e->y, e->t }; - return p; - } -}; - -Stroke::Stroke(PreStroke &s, int trigger_, int button_, bool timeout_) : button(button_), timeout(timeout_) { - trigger = (trigger_ == get_default_button()) ? 0 : trigger_; - - if (s.valid()) { - std::transform(s.begin(), s.end(), std::back_inserter(points), f()); - normalize(); - } -} - -void Stroke::normalize() { - if (0) { - double first = points.front().time; - double length = points.back().time - first; - for (vector::iterator i = points.begin(); i != points.end(); i++) { - i->time -= first; - i->time /= length; - } - } else { - double total = 0; - double lastx = 0; - double lasty = 0; - bool first = true; - for (vector::iterator i = points.begin(); i != points.end(); i++) { - if (first) { - i->time = 0; - lastx = i->x; - lasty = i->y; - first = false; - continue; - } - total += hypot(i->x-lastx, i->y-lasty); - i->time = total; - lastx = i->x; - lasty = i->y; - } - for (vector::iterator i = points.begin(); i != points.end(); i++) { - i->time /= total; - } - } - - double minX=0, minY=0, maxX=0, maxY=0; - bool first = true; - for (vector::iterator i = points.begin(); i!=points.end();i++) { - if (first) { - minX = i->x; - maxX = i->x; - minY = i->y; - maxY = i->y; - first = false; - } else { - if (i->x < minX) minX = i->x; - if (i->x > maxX) maxX = i->x; - if (i->y < minY) minY = i->y; - if (i->y > maxY) maxY = i->y; - } - } - double scaleX = maxX - minX; - double scaleY = maxY - minY; - double scale = (scaleX > scaleY) ? scaleX : scaleY; - if (scale < 0.001) scale = 1; - for (vector::iterator i = points.begin(); i != points.end(); i++) { - i->x = (i->x-(minX+maxX)/2)/scale + 0.5; - i->y = (i->y-(minY+maxY)/2)/scale + 0.5; - } -} - -void Stroke::print() const { - printf("button: %d\n", button); - for (vector::const_iterator i = points.begin(); i != points.end(); i++) { - printf("pt: (%f, %f) at %f\n", i->x, i->y, i->time); - } -} - -double Stroke::length() const { - double length = 0; - bool first = true; - double lastx = 0; - double lasty = 0; - for (vector::const_iterator i = points.begin(); i != points.end(); i++) { - if (first) { - lastx = i->x; - lasty = i->y; - first = false; - continue; - } - length += hypot(i->x-lastx, i->y-lasty); - lastx = i->x; - lasty = i->y; - } - return length; -} - -/********* Iterators **********/ -struct Pt { - double x; double y; -}; - -struct PtPair { - double t; - Pt a; - Pt b; -}; - -class Stroke::RefineIterator { - vector::const_iterator i, i_end, j, j_end; -public: - RefineIterator(RStroke a, RStroke b) : - i(a->points.begin()), i_end(a->points.end()), j(b->points.begin()), j_end(b->points.end()) {} - double operator++(int) { - if (close(i->time, j->time)) { - double current = (i->time + j->time)/2; - i++; j++; - return current; - } - if (i->time < j->time) { - double current = i->time; - i++; - return current; - } else { - double current = j->time; - j++; - return current; - } - } - operator bool() { - return i != i_end && j != j_end; - } -}; - -class Stroke::InterpolateIterator { - RefineIterator &t; - vector::const_iterator j, j_end; - const Point *a, *b; // The current line segment goes from a to b - Pt current; -public: - InterpolateIterator(RefineIterator &t_, RStroke &in) : - t(t_), - j(in->points.begin()), - j_end(in->points.end()), - a(0), b(0) - {} - operator bool() { return t; } - Pt operator++(int) { - double now = t++; - while (j != j_end && (!a || b->time < now)) { - a = b; - b = &(*j); - j++; - } - double delta = b->time - a->time; - if (delta < eps) { - current.x = b->x; - current.y = b->y; - } else { - double k = (now - a->time) / delta; - current.x = a->x + (b->x - a->x) * k; - current.y = a->y + (b->y - a->y) * k; - } - return current; - } -}; - -class Stroke::RIIterator { - // There is more potential for optimization here - RefineIterator t; - RefineIterator ti; - RefineIterator tj; - InterpolateIterator i, j; - PtPair p; -public: - RIIterator(RStroke a, RStroke b) : - t(a, b), - ti(a, b), - tj(a, b), - i(ti, a), - j(tj, b) - {} - operator bool() { return t; } - PtPair operator++(int) { - p.t = t++; - p.a = i++; - p.b = j++; - return p; - } -}; - -class Stroke::DiffIntegral : public EasyIterator { - RIIterator i; - double a_length, b_length; -public: - DiffIntegral(RStroke a, RStroke b) : i(a, b), a_length(a->length()), b_length(b->length()) {} - inline virtual const Point operator++(int) { - PtPair ps = i++; - Point p; - p.x = ps.a.x/a_length - ps.b.x/b_length; - p.y = ps.a.y/a_length - ps.b.y/b_length; - p.time = ps.t; - return p; - } - inline virtual operator bool() { return i; } -}; - -class Stroke::StrokeIntegral : public EasyIterator { - vector::const_iterator i, i_end; -public: - StrokeIntegral(const Stroke& s) : i(s.points.begin()), i_end(s.points.end()) {} - inline virtual const Point operator++(int) { return *(i++); } - inline virtual operator bool() { return i != i_end; } -}; - -/******** (Iterators) *********/ - -Integral Stroke::diff_integral(RStroke a, RStroke b) { - DiffIntegral di(a, b); - return integral(di); -} - -Integral Stroke::integral() const{ - StrokeIntegral si(*this); - return integral(si); -} - -Integral Stroke::integral(EasyIterator& i) { - Integral sum = {{{0,0},{0,0}},{{0,0},{0,0}}}; - Point a; - Point b = i++; - while (i) { - a = b; - b = i++; - double delta = b.time - a.time; - if (delta < eps) - continue; -#define INT_II(l, c) delta * (c + l) / 2 - sum.i.i.x += INT_II(a.x, b.x); - sum.i.i.y += INT_II(a.y, b.y); -#undef INT_II -#define INT_IS(l, c) delta * (c*(c+l)+l*l) / 3 - sum.i.s.x += INT_IS(a.x, b.x); - sum.i.s.y += INT_IS(a.y, b.y); -#undef INT_IS - sum.d.i.x += b.x - a.x; - sum.d.i.y += b.y - a.y; -#define INT_DS(l, c) sqr(c-l)/delta - sum.d.s.x += INT_DS(a.x, b.x); - sum.d.s.y += INT_DS(a.y, b.y); -#undef INT_DS - } - return sum; -} - -void Stroke::integral2(RStroke a, RStroke b, double &int_x, double &int_y, double &int_dx, double &int_dy) { - PtPair cur = { 0, {0,0}, {0,0} }; - PtPair last = { 0, {0,0}, {0,0} }; - int_x = 0; int_y = 0; int_dx = 0; int_dy = 0; - - for (RIIterator i(a, b); i;) { - last = cur; - cur = i++; - double delta = cur.t - last.t; - int_x += delta*(2*cur.a.x*cur.b.x+2*last.a.x*last.b.x+cur.a.x*last.b.x+cur.b.x*last.a.x)/6; - int_y += delta*(2*cur.a.y*cur.b.y+2*last.a.y*last.b.y+cur.a.y*last.b.y+cur.b.y*last.a.y)/6; - if (delta < eps) - continue; - int_dx += (cur.a.x-last.a.x)*(cur.b.x-last.b.x)/delta; - int_dy += (cur.a.y-last.a.y)*(cur.b.y-last.b.y)/delta; - } -} - -bool Stroke::compare(RStroke a_, RStroke b_, double &score) { - score = -2; - if (!a_ || !b_) - return false; - if (!a_->timeout != !b_->timeout) - return false; - if (a_->button != b_->button) - return false; - if (a_->trigger != b_->trigger) { - if (a_->trigger && b_->trigger) - return false; - if (a_->trigger + b_->trigger != get_default_button()) - return false; - } - if (a_->size() == 0 || b_->size() == 0) { - if (a_->size() == 0 && b_->size() == 0) { - score = 1; - return true; - } - else - return false; - } - double ab_x, ab_y, dab_x, dab_y; - integral2(a_, b_, ab_x, ab_y, dab_x, dab_y); - Integral a = a_->integral(); - Integral b = b_->integral(); - double A = (a.i.s.x - sqr(a.i.i.x)) + (a.i.s.y - sqr(a.i.i.y)); - double B = (b.i.s.x - sqr(b.i.i.x)) + (b.i.s.y - sqr(b.i.i.y)); - double C = (ab_x - a.i.i.x * b.i.i.x) + (ab_y - a.i.i.y * b.i.i.y); - double X = a.d.s.x + a.d.s.y; - double Y = b.d.s.x + b.d.s.y; - double Z = dab_x + dab_y; - - double p = prefs.p.get(); - double q = 1 - p; - score = (q*C/A+p*Z/X)/sqrt(q*B/A+p*Y/X); - if (a_->timeout) - return score > 0.85; - else - return score > 0.7; -} - -Glib::RefPtr Stroke::draw(int size, double width) const { - if (size != STROKE_SIZE || (width != 2.0 && width != 4.0)) - return draw_(size, width); - int i = width == 2.0; - if (pb[i]) - return pb[i]; - pb[i] = draw_(size, width); - return pb[i]; -} - -Glib::RefPtr Stroke::pbEmpty; - -Glib::RefPtr Stroke::drawEmpty(int size) { - if (size != STROKE_SIZE) - return drawEmpty_(size); - if (pbEmpty) - return pbEmpty; - pbEmpty = drawEmpty_(size); - return pbEmpty; -} - - -RStroke Stroke::trefoil() { - PreStroke s; - const int n = 40; - const double pi = 3.141592653589793238462643; - for (int i = 0; i<=n; i++) { - double phi = pi*(-4.0*i/n)-2.7; - double r = exp(1.0 + sin(6.0*pi*i/n)) + 2.0; - s.add(create_triple(r*cos(phi), r*sin(phi), i)); - } - return Stroke::create(s, 0, 0, false); -} diff --git a/stroke.h b/stroke.h index 2d5370a6..5eca12ef 100644 --- a/stroke.h +++ b/stroke.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2009, Thomas Jaeger + * Copyright (c) 2009, Thomas Jaeger * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -16,127 +16,30 @@ #ifndef __STROKE_H__ #define __STROKE_H__ -#include -#include -#include -#include -#include - -#include // Time - -#define STROKE_SIZE 64 - -class Stroke; -class PreStroke; - -typedef boost::shared_ptr RStroke; -typedef boost::shared_ptr RPreStroke; - -struct Integral { - struct { - struct { - double x; - double y; - } s, i; // square or not - } d, i; // derivative or not -}; - -template class EasyIterator { -public: - virtual const T operator++(int) = 0; - virtual operator bool() = 0; -}; - -int get_default_button(); - -struct Triple { - float x; - float y; - Time t; -}; -typedef boost::shared_ptr RTriple; -void update_triple(RTriple e, float x, float y, Time t); -RTriple create_triple(float x, float y, Time t); - -class PreStroke; -class Stroke { - friend class PreStroke; - friend class boost::serialization::access; -public: - struct Point { - double x; - double y; - double time; - template void serialize(Archive & ar, const unsigned int version) { - ar & x; ar & y; ar & time; - } - }; - -private: - class RefineIterator; - class InterpolateIterator; - class RIIterator; - - class StrokeIntegral; - class DiffIntegral; - static inline Integral integral(EasyIterator&); - Integral integral() const; - static Integral diff_integral(RStroke a, RStroke b); - static void integral2(RStroke a, RStroke b, double &int_x, double &int_y, double &int_dx, double &int_dy); - double length() const; - int size() const { return points.size(); } - - Stroke(PreStroke &s, int trigger_, int button_, bool timeout_); - - Glib::RefPtr draw_(int size, double width = 2.0) const; - mutable Glib::RefPtr pb[2]; - std::vector points; +#ifdef __cplusplus +extern "C" { +#endif - static Glib::RefPtr drawEmpty_(int); - static Glib::RefPtr pbEmpty; +struct _stroke_t; - template void serialize(Archive & ar, const unsigned int version) { - ar & points; - if (version == 0) return; - ar & button; - if (version >= 2) - ar & trigger; - if (version < 4 && (!button || trigger == get_default_button())) - trigger = 0; - if (version < 3) - return; - ar & timeout; - } +typedef struct _stroke_t stroke_t; -public: - int trigger; - int button; - bool timeout; +stroke_t *stroke_alloc(int n); +void stroke_add_point(stroke_t *stroke, double x, double y); +void stroke_finish(stroke_t *stroke); +void stroke_free(stroke_t *stroke); - Stroke() : trigger(0), button(0), timeout(false) {} - static RStroke create(PreStroke &s, int trigger_, int button_, bool timeout_) { - return RStroke(new Stroke(s, trigger_, button_, timeout_)); - } - Glib::RefPtr draw(int size, double width = 2.0) const; - void draw(Cairo::RefPtr surface, int x, int y, int w, int h, double width = 2.0) const; - void draw_svg(std::string filename) const; - bool show_icon(); +int stroke_get_size(const stroke_t *stroke); +void stroke_get_point(const stroke_t *stroke, int n, double *x, double *y); +double stroke_get_time(const stroke_t *stroke, int n); +double stroke_get_angle(const stroke_t *stroke, int n); +double stroke_angle_difference(const stroke_t *a, const stroke_t *b, int i, int j); - static RStroke trefoil(); - static bool compare(RStroke, RStroke, double &); - static Glib::RefPtr drawEmpty(int); +double stroke_compare(const stroke_t *a, const stroke_t *b, int *path_x, int *path_y); - void print() const; - void normalize(); - bool trivial() const { return size() == 0 && button == 0; } - bool is_timeout() const { return timeout; } -}; -BOOST_CLASS_VERSION(Stroke, 4) +extern const double stroke_infinity; -class PreStroke : public std::vector { -public: - static RPreStroke create() { return RPreStroke(new PreStroke()); } - void add(RTriple p) { push_back(p); } - bool valid() const { return size() > 2; } -}; +#ifdef __cplusplus +} +#endif #endif diff --git a/util.cc b/util.cc index c3df001c..6fe0d92b 100644 --- a/util.cc +++ b/util.cc @@ -21,5 +21,5 @@ void show_us(const char *str) { struct timeval tv; gettimeofday(&tv, 0); - printf("%s: %ld\n", str, tv.tv_usec); + printf("%s: %ld.%ld\n", str, tv.tv_sec, tv.tv_usec); } diff --git a/win.cc b/win.cc index 128917b4..e208bb5b 100644 --- a/win.cc +++ b/win.cc @@ -31,34 +31,27 @@ void Stroke::draw(Cairo::RefPtr surface, int x, int y, int w, in ctx->set_line_width(2.0*width/(w+h)); if (size()) { ctx->set_line_cap(Cairo::LINE_CAP_ROUND); - int n = points.size(); + int n = size(); float lambda = sqrt(3)-2.0; float sum = lambda / (1 - lambda); std::vector y(n); - y[0].x = sum * points[0].x; - y[0].y = sum * points[0].y; - for (int j = 0; j < n-1; j++) { - y[j+1].x = lambda * (y[j].x + points[j].x); - y[j+1].y = lambda * (y[j].y + points[j].y); - } + y[0] = points(0) * sum; + for (int j = 0; j < n-1; j++) + y[j+1] = (y[j] + points(j)) * lambda; std::vector z(n); - z[n-1].x = -1.0 * sum * points[n-1].x; - z[n-1].y = -1.0 * sum * points[n-1].y; - for (int j = n-1; j > 0; j--) { - z[j-1].x = lambda * (z[j].x - points[j].x); - z[j-1].y = lambda * (z[j].y - points[j].y); - } + z[n-1] = points(n-1) * (-sum); + for (int j = n-1; j > 0; j--) + z[j-1] = (z[j] - points(j)) * lambda; for (int j = 0; j < n-1; j++) { // j -> j+1 - ctx->set_source_rgba(0, points[j].time, 1-points[j].time, 1); - ctx->move_to(points[j].x, points[j].y); - ctx->curve_to( - points[j].x + y[j].x + z[j].x, - points[j].y + y[j].y + z[j].y, - points[j+1].x - y[j+1].x - z[j+1].x, - points[j+1].y - y[j+1].y - z[j+1].y, - points[j+1].x, - points[j+1].y); + ctx->set_source_rgba(0, time(j), 1-time(j), 1); + Point p[4]; + p[0] = points(j); + p[3] = points(j+1); + p[1] = p[0] + y[j] + z[j]; + p[2] = p[3] - y[j+1] - z[j+1]; + ctx->move_to(p[0].x, p[0].y); + ctx->curve_to(p[1].x, p[1].y, p[2].x, p[2].y, p[3].x, p[3].y); ctx->stroke(); } } else if (!button) { diff --git a/win.h b/win.h index b9b19cb1..16e21d84 100644 --- a/win.h +++ b/win.h @@ -15,7 +15,7 @@ */ #ifndef __WIN_H__ #define __WIN_H__ -#include "stroke.h" +#include "gesture.h" #include #include "util.h" #include "prefdb.h"