285 lines
8.1 KiB
C++
285 lines
8.1 KiB
C++
////////////////////////////////////////////////////////////////////////////////
|
|
// task - a command line task list manager.
|
|
//
|
|
// Copyright 2006 - 2010, Paul Beckingham.
|
|
// All rights reserved.
|
|
//
|
|
// This program is free software; you can redistribute it and/or modify it under
|
|
// the terms of the GNU General Public License as published by the Free Software
|
|
// Foundation; either version 2 of the License, or (at your option) any later
|
|
// version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful, but WITHOUT
|
|
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
// details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License along with
|
|
// this program; if not, write to the
|
|
//
|
|
// Free Software Foundation, Inc.,
|
|
// 51 Franklin Street, Fifth Floor,
|
|
// Boston, MA
|
|
// 02110-1301
|
|
// USA
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
#include <map>
|
|
#include <vector>
|
|
#include <string>
|
|
#include <stdlib.h>
|
|
#include "Rectangle.h"
|
|
#include "Tree.h"
|
|
#include "Lisp.h"
|
|
|
|
static void recalcNode (Tree*, int, int, int, int);
|
|
static void distribute (const int, const std::vector <std::string>&, std::vector <int>&);
|
|
static void getRequests (Tree*, std::vector <std::string>&);
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void resize (
|
|
const std::string& input,
|
|
const int width,
|
|
const int height,
|
|
std::string& name,
|
|
std::map <std::string, Rectangle>& panels)
|
|
{
|
|
// Parse the input into a tree.
|
|
Lisp l;
|
|
Tree* root = l.parse (input);
|
|
|
|
// Navigate the tree and calculate sizes. This requires a breadth-first
|
|
// traversal, so that calculations at one level are then propagated down.
|
|
// After all, the window dimensions dictate the layout size, and so on down.
|
|
Tree* layout = (*root)[0];
|
|
layout->attribute ("left", 0);
|
|
layout->attribute ("top", 0);
|
|
layout->attribute ("width", width);
|
|
layout->attribute ("height", height);
|
|
recalcNode (layout, 0, 0, width, height);
|
|
|
|
// Extract the name of the layout.
|
|
std::vector <std::string> tags = layout->allTags ();
|
|
if (tags.size () >= 2)
|
|
name = tags[1];
|
|
else
|
|
name = "anonymous";
|
|
|
|
// Extract panels.
|
|
panels.clear ();
|
|
std::vector <Tree*> nodes;
|
|
layout->enumerate (nodes);
|
|
std::vector <Tree*>::iterator it;
|
|
for (it = nodes.begin (); it != nodes.end (); ++it)
|
|
{
|
|
tags = (*it)->allTags ();
|
|
if (tags[0] == "panel")
|
|
panels[tags[1]] =
|
|
Rectangle (
|
|
atoi ((*it)->attribute ("left").c_str ()),
|
|
atoi ((*it)->attribute ("top").c_str ()),
|
|
atoi ((*it)->attribute ("width").c_str ()),
|
|
atoi ((*it)->attribute ("height").c_str ()));
|
|
}
|
|
|
|
delete root;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Specified node has associated width and height. Subdivide either width or
|
|
// height across all tree branches depending on which container the parent is.
|
|
static void recalcNode (Tree* parent, int left, int top, int width, int height)
|
|
{
|
|
// What kind of parent is this? layout, horizontal or vertical?
|
|
std::string parent_type = "?";
|
|
std::vector <std::string> parent_tags = parent->allTags ();
|
|
if (parent_tags.size ())
|
|
parent_type = parent_tags[0];
|
|
else
|
|
throw std::string ("Error: node has no specified type");
|
|
|
|
if (parent_type == "horizontal")
|
|
{
|
|
std::vector <std::string> requested;
|
|
getRequests (parent, requested);
|
|
|
|
std::vector <int> allocated;
|
|
distribute (width, requested, allocated);
|
|
|
|
int current_left = left;
|
|
for (int i = 0; i < parent->branches (); ++i)
|
|
{
|
|
(*parent)[i]->attribute ("left", current_left);
|
|
(*parent)[i]->attribute ("top", top);
|
|
(*parent)[i]->attribute ("width", allocated[i]);
|
|
(*parent)[i]->attribute ("height", height);
|
|
current_left += allocated[i];
|
|
}
|
|
}
|
|
|
|
else if (parent_type == "vertical")
|
|
{
|
|
std::vector <std::string> requested;
|
|
getRequests (parent, requested);
|
|
|
|
std::vector <int> allocated;
|
|
distribute (height, requested, allocated);
|
|
|
|
int current_top = top;
|
|
for (int i = 0; i < parent->branches (); ++i)
|
|
{
|
|
(*parent)[i]->attribute ("left", left);
|
|
(*parent)[i]->attribute ("top", current_top);
|
|
(*parent)[i]->attribute ("width", width);
|
|
(*parent)[i]->attribute ("height", allocated[i]);
|
|
current_top += allocated[i];
|
|
}
|
|
}
|
|
|
|
else if (parent_type == "layout")
|
|
{
|
|
if (! (*parent)[0])
|
|
throw std::string ("Error: layout has no contents.");
|
|
|
|
(*parent)[0]->attribute ("left", left);
|
|
(*parent)[0]->attribute ("top", top);
|
|
(*parent)[0]->attribute ("width", width);
|
|
(*parent)[0]->attribute ("height", height);
|
|
}
|
|
|
|
// Now recurse to each branch of parent that is a container.
|
|
for (int i = 0; i < parent->branches (); ++i)
|
|
{
|
|
Tree* child = (*parent)[i];
|
|
if (child->hasTag ("horizontal") ||
|
|
child->hasTag ("vertical"))
|
|
{
|
|
recalcNode (child,
|
|
atoi (child->attribute ("left").c_str ()),
|
|
atoi (child->attribute ("top").c_str ()),
|
|
atoi (child->attribute ("width").c_str ()),
|
|
atoi (child->attribute ("height").c_str ()));
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// If the call looks like this:
|
|
// distribute (60, [10, *, *, *, *, *, 50%], [])
|
|
//
|
|
// Then the result is:
|
|
// distribute (60, [10, *, 50%], [10, 5, 5, 5, 5, 5, 25])
|
|
//
|
|
// A literal number is a request. It will be granted, provided there is space.
|
|
// A percentage is a request for a portion of the unallocated amount.
|
|
// A wildcard is a request for an equal share, among all wildcards, of the
|
|
// unallocated amount.
|
|
//
|
|
static void distribute (
|
|
const int total,
|
|
const std::vector <std::string>& input,
|
|
std::vector <int>& output)
|
|
{
|
|
int allocated = 0;
|
|
int unallocated = total;
|
|
|
|
output.clear ();
|
|
|
|
// First count up the requested.
|
|
for (unsigned int i = 0; i < input.size (); ++i)
|
|
if (input[i] == "*" || input[i].find ("%") != std::string::npos)
|
|
output.push_back (0);
|
|
else
|
|
{
|
|
int value = atoi (input[i].c_str ());
|
|
output.push_back (value);
|
|
allocated += value;
|
|
unallocated -= value;
|
|
}
|
|
|
|
if (allocated > total)
|
|
throw std::string ("Error: over allocation by request.");
|
|
|
|
// Now include the proportional.
|
|
int before_allocated = allocated;
|
|
for (unsigned int i = 0; i < input.size (); ++i)
|
|
{
|
|
if (input[i].find ("%") != std::string::npos)
|
|
{
|
|
int value = atoi (input[i].c_str ());
|
|
value = (value * unallocated) / 100;
|
|
output[i] = value;
|
|
|
|
allocated += value;
|
|
}
|
|
}
|
|
|
|
unallocated -= (allocated - before_allocated);
|
|
|
|
if (allocated > total)
|
|
throw std::string ("Error: over allocation by request.");
|
|
|
|
// Count the wildcards.
|
|
int wildcards = 0;
|
|
for (unsigned int i = 0; i < input.size (); ++i)
|
|
if (input[i] == "*")
|
|
++wildcards;
|
|
|
|
// Evenly distribute unallocated among the wildcards.
|
|
for (unsigned int i = 0; i < input.size (); ++i)
|
|
{
|
|
if (input[i] == "*")
|
|
{
|
|
if (wildcards > 1)
|
|
{
|
|
int portion = unallocated / wildcards;
|
|
--wildcards;
|
|
|
|
output[i] = portion;
|
|
|
|
allocated += portion;
|
|
unallocated -= portion;
|
|
}
|
|
else
|
|
{
|
|
output[i] = unallocated;
|
|
allocated += unallocated;
|
|
unallocated = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
static void getRequests (Tree* node, std::vector <std::string>& requested)
|
|
{
|
|
requested.clear ();
|
|
|
|
for (int i = 0; i < node->branches (); ++i)
|
|
{
|
|
Tree* child = (*node)[i];
|
|
std::vector <std::string> child_tags = child->allTags ();
|
|
|
|
switch (child_tags.size ())
|
|
{
|
|
case 1: // (xcontainer (...))
|
|
requested.push_back ("*");
|
|
break;
|
|
|
|
case 2: // (xcontainer size (...))
|
|
requested.push_back (child_tags[1]);
|
|
break;
|
|
|
|
case 3: // (panel name size)
|
|
requested.push_back (child_tags[2]);
|
|
break;
|
|
|
|
default:
|
|
throw std::string ("Error: unexpected number of tags in a node");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|