diff --git a/src/data/statistics.cpp b/src/data/statistics.cpp index 742ff26f..0e0a3b21 100644 --- a/src/data/statistics.cpp +++ b/src/data/statistics.cpp @@ -13,6 +13,7 @@ StatsDimension::StatsDimension() : automatic(false) + , numeric(false) {} StatsDimension::StatsDimension(const Field& field) @@ -20,6 +21,7 @@ StatsDimension::StatsDimension(const Field& field) , name (field.name) , description (field.description) , icon_filename(field.icon_filename) + , numeric(false) { // initialize script, card.{field_name} Script& s = script.getScript(); @@ -34,6 +36,7 @@ IMPLEMENT_REFLECTION(StatsDimension) { REFLECT(description); REFLECT_N("icon", icon_filename); REFLECT(script); + REFLECT(numeric); } } diff --git a/src/data/statistics.hpp b/src/data/statistics.hpp index 9907c76f..c986ab22 100644 --- a/src/data/statistics.hpp +++ b/src/data/statistics.hpp @@ -30,6 +30,7 @@ class StatsDimension { String description; ///< Description, used in status bar String icon_filename; ///< Icon for lists OptionalScript script; ///< Script that determines the value(s) + bool numeric; ///< Are the values numeric? If so, they require special sorting bool automatic; ///< Based on a card field? DECLARE_REFLECTION(); diff --git a/src/gfx/gfx.hpp b/src/gfx/gfx.hpp index cf355697..b0971cca 100644 --- a/src/gfx/gfx.hpp +++ b/src/gfx/gfx.hpp @@ -68,9 +68,6 @@ void linear_blend(Image& img1, const Image& img2, double x1,double y1, double x2 */ void mask_blend(Image& img1, const Image& img2, const Image& mask); -/// Use the red channel of img_alpha as alpha channel for img -void set_alpha(Image& img, const Image& img_alpha); - // ----------------------------------------------------------------------------- : Effects /// Saturate an image, amount should be in range [0...100] @@ -116,16 +113,27 @@ void draw_combine_image(DC& dc, UInt x, UInt y, const Image& img, ImageCombine c // ----------------------------------------------------------------------------- : Masks +/// Use the red channel of img_alpha as alpha channel for img +void set_alpha(Image& img, const Image& img_alpha); + /// An alpha mask is an alpha channel that can be copied to another image /** It is created by treating black in the source image as transparent and white (red) as opaque */ class AlphaMask { public: - AlphaMask(); + AlphaMask(const Image& mask); ~AlphaMask(); - // TODO + /// Apply the alpha mask to an image + void setAlpha(Image& i) const; + /// Apply the alpha mask to a bitmap + void setAlpha(Bitmap& b) const; + /// Is the given location fully transparent? + bool isTransparent(int x, int y) const; + + /// Size of the mask + wxSize size; private: Byte* alpha; }; diff --git a/src/gui/control/card_editor.cpp b/src/gui/control/card_editor.cpp index 3d1b7b94..8cf9760e 100644 --- a/src/gui/control/card_editor.cpp +++ b/src/gui/control/card_editor.cpp @@ -126,6 +126,8 @@ void DataEditor::createTabIndex() { } void DataEditor::onInit() { createTabIndex(); + current_viewer = nullptr; + current_editor = nullptr; } // ----------------------------------------------------------------------------- : Clipboard & Formatting diff --git a/src/gui/control/graph.cpp b/src/gui/control/graph.cpp index 243a15fd..7733176a 100644 --- a/src/gui/control/graph.cpp +++ b/src/gui/control/graph.cpp @@ -42,9 +42,36 @@ GraphData::GraphData(const GraphDataPre& d) counts[e->values[i]] += 1; } // TODO: allow some ordering in the groups, and allow colors to be passed - FOR_EACH(c, counts) { - a->groups.push_back(GraphGroup(c.first, c.second)); - a->max = max(a->max, c.second); + if (a->numeric) { + // TODO: start at something other than 0? + // TODO: support fractions? + size_t left = counts.size(); + int i = 0; + while (left) { + String is = String() << i++; + map::const_iterator it = counts.find(is); + if (it == counts.end()) { + // not found, add a 0 bar + a->groups.push_back(GraphGroup(is, 0)); + } else { + a->groups.push_back(GraphGroup(is, it->second)); + a->max = max(a->max, it->second); + left--; + } + if (i > 100) { + // prevent infinite loops if there are non-numeric entries + // drop empty tail + while (a->groups.size() > 1 && a->groups.back().size == 0) { + a->groups.pop_back(); + } + break; + } + } + } else { + FOR_EACH(c, counts) { + a->groups.push_back(GraphGroup(c.first, c.second)); + a->max = max(a->max, c.second); + } } // find some nice colors for the groups if (a->auto_color) { @@ -106,7 +133,7 @@ bool Graph1D::findItem(const RealPoint& pos, const RealRect& rect, vector& void BarGraph::draw(RotatedDC& dc, int current) const { if (!data) return; // Rectangle for bars - RealRect rect = dc.getInternalRect().move(15, 5, -20, -20); + RealRect rect = dc.getInternalRect().move(23, 8, -30, -28); // Bar sizes GraphAxis& axis = axis_data(); int count = int(axis.groups.size()); @@ -151,10 +178,10 @@ void BarGraph::draw(RotatedDC& dc, int current) const { FOR_EACH_CONST(g, axis.groups) { // draw bar dc.SetBrush(g.color); - dc.DrawRectangle(RealRect(x + space / 2, rect.bottom() + 1, width, -step_height * g.size - 1.999)); + dc.DrawRectangle(RealRect(x + space / 2, rect.bottom() + 1, width, (int)(rect.bottom() - step_height * g.size) - rect.bottom() - 1)); // draw label, aligned bottom center RealSize text_size = dc.GetTextExtent(g.name); - dc.SetClippingRegion(RealRect(x + 2, rect.bottom(), width_space - 4, text_size.height)); + dc.SetClippingRegion(RealRect(x + 2, rect.bottom() + 3, width_space - 4, text_size.height)); dc.DrawText(g.name, align_in_rect(ALIGN_TOP_CENTER, text_size, RealRect(x, rect.bottom() + 3, width_space, 0))); dc.DestroyClippingRegion(); x += width_space; @@ -163,7 +190,7 @@ void BarGraph::draw(RotatedDC& dc, int current) const { int BarGraph::findItem(const RealPoint& pos, const RealRect& rect1) const { if (!data) return -1; // Rectangle for bars - RealRect rect = rect1.move(15, 5, -20, -20); + RealRect rect = rect1.move(23, 8, -30, -28); // Bar sizes GraphAxis& axis = axis_data(); int count = int(axis.groups.size()); diff --git a/src/gui/control/graph.hpp b/src/gui/control/graph.hpp index f2a46067..30546fc5 100644 --- a/src/gui/control/graph.hpp +++ b/src/gui/control/graph.hpp @@ -36,15 +36,17 @@ class GraphGroup { /** The sum of groups.sum = sum of all elements in the data */ class GraphAxis { public: - GraphAxis(const String& name, bool auto_color = true) + GraphAxis(const String& name, bool auto_color = true, bool numeric = false) : name(name) , auto_color(auto_color) , max(0) + , numeric(numeric) {} String name; ///< Name/label of this axis bool auto_color; ///< Automatically assign colors to the groups on this axis vector groups; ///< Groups along this axis + bool numeric; ///< Numeric axis? UInt max; ///< Maximum size of the groups }; diff --git a/src/gui/control/package_list.cpp b/src/gui/control/package_list.cpp index 8d99781a..204c5407 100644 --- a/src/gui/control/package_list.cpp +++ b/src/gui/control/package_list.cpp @@ -54,9 +54,10 @@ void PackageList::showData(const String& pattern) { PackageP package = ::packages.openAny(f); // open image InputStreamP stream = package->openIconFile(); + Image img; Bitmap bmp; - if (stream) { - bmp = Bitmap(Image(*stream)); + if (stream && img.LoadFile(*stream)) { + bmp = Bitmap(img); } // add to list packages.push_back(PackageData(package, bmp)); diff --git a/src/gui/set/stats_panel.cpp b/src/gui/set/stats_panel.cpp index 5ac419b3..df67509b 100644 --- a/src/gui/set/stats_panel.cpp +++ b/src/gui/set/stats_panel.cpp @@ -114,7 +114,7 @@ void StatsPanel::onCommand(int id) { StatsCategory& cat = categories->getSelection(); GraphDataPre d; FOR_EACH(dim, cat.dimensions) { - d.axes.push_back(new_shared1(dim->name)); + d.axes.push_back(new_shared3(dim->name, true, dim->numeric)); } FOR_EACH(card, set->cards) { Context& ctx = set->getContext(card); diff --git a/src/mse.vcproj b/src/mse.vcproj index 86ebc8cf..2543b538 100644 --- a/src/mse.vcproj +++ b/src/mse.vcproj @@ -1978,6 +1978,9 @@ + + diff --git a/src/render/value/image.cpp b/src/render/value/image.cpp index 5938e820..6341f586 100644 --- a/src/render/value/image.cpp +++ b/src/render/value/image.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include // ----------------------------------------------------------------------------- : ImageValueViewer @@ -23,9 +24,9 @@ void ImageValueViewer::draw(RotatedDC& dc) { if (image.LoadFile(*image_file)) { image.Rescale(dc.trS(style().width), dc.trS(style().height)); // apply mask to image -/* loadMask(dc); + loadMask(dc); if (alpha_mask) alpha_mask->setAlpha(image); -*/ bitmap = Bitmap(image); + bitmap = Bitmap(image); } } catch (Error e) { handle_error(e, false, false); // don't handle now, we are in onPaint @@ -34,15 +35,9 @@ void ImageValueViewer::draw(RotatedDC& dc) { // if there is no image, generate a placeholder, only if there is enough room for it if (!bitmap.Ok() && style().width > 40) { bitmap = imagePlaceholder(dc, dc.trS(style().width), dc.trS(style().height), viewer.drawEditing()); -/* loadMask(dc); + loadMask(dc); if (alpha_mask) alpha_mask->setAlpha(bitmap); -/* if (alphaMask) { - // convert to image and apply alpha - Image image = bmp.ConvertToImage(); - alpha_mask->setAlpha(image); - bitmap = image; - } -*/ } + } // draw image, if any if (bitmap.Ok()) { dc.DrawBitmap(bitmap, style().getPos()); @@ -55,15 +50,13 @@ bool ImageValueViewer::containsPoint(const RealPoint& p) const { if (x < 0 || y < 0 || x >= (int)style().width || y >= (int)style().height) { return false; // outside rectangle } -/* // check against mask - if (!style->maskFilename.value.empty()) { - RotatedObject rot(viewer.getRotation()); - loadMask(rot); - return !alphaMask->isTransparent(x, y); + // check against mask + if (!style().mask_filename().empty()) { + loadMask(viewer.getRotation()); + return !alpha_mask || !alpha_mask->isTransparent(x, y); } else { return true; - }*/ - return true; + } } void ImageValueViewer::onValueChange() { @@ -72,7 +65,20 @@ void ImageValueViewer::onValueChange() { void ImageValueViewer::onStyleChange() { bitmap = Bitmap(); -// alpha_mask = AlphaMaskP(); + alpha_mask = AlphaMaskP(); +} + +void ImageValueViewer::loadMask(const Rotation& rot) const { + if (style().mask_filename().empty()) return; // no mask + if (alpha_mask && alpha_mask->size == wxSize(rot.trS(style().width), rot.trS(style().height))) return; // mask loaded and right size + // (re) load the mask + Image image; + InputStreamP image_file = viewer.stylesheet->openIn(style().mask_filename); + if (image.LoadFile(*image_file)) { + Image resampled(rot.trS(style().width), rot.trS(style().height)); + resample(image, resampled); + alpha_mask = new_shared1(resampled); + } } Bitmap ImageValueViewer::imagePlaceholder(const Rotation& rot, UInt w, UInt h, bool editing) { diff --git a/src/render/value/image.hpp b/src/render/value/image.hpp index 32893532..7426c27c 100644 --- a/src/render/value/image.hpp +++ b/src/render/value/image.hpp @@ -13,6 +13,8 @@ #include #include +DECLARE_POINTER_TYPE(AlphaMask); + // ----------------------------------------------------------------------------- : ImageValueViewer /// Viewer that displays an image value @@ -29,9 +31,9 @@ class ImageValueViewer : public ValueViewer { private: Bitmap bitmap; -// mutable AlphaMaskP alpha_mask; + mutable AlphaMaskP alpha_mask; -// void loadMask(const RotatedObject& rot) const; + void loadMask(const Rotation& rot) const; /// Generate a placeholder image static Bitmap imagePlaceholder(const Rotation& rot, UInt w, UInt h, bool editing); diff --git a/src/util/rotation.hpp b/src/util/rotation.hpp index 7c4e29f1..b89d3843 100644 --- a/src/util/rotation.hpp +++ b/src/util/rotation.hpp @@ -42,7 +42,7 @@ class Rotation { RealRect getExternalRect() const; /// Translate a size or length - inline double trS(double s) const { return s * zoom; } + inline double trS(double s) const { return s * zoom; } /// Translate a single point RealPoint tr(const RealPoint& p) const;