void drawRow(Graphics gTo, int windowWidth, int xStart, RowParameters rowParameters, int rowHeight, String msg, int row, String state, boolean hard, Color bgColor, float seeThrough, BufferedImage sparkLine, boolean isFlapping)
	{
		if (windowWidth == 0)
			return;

		BufferedImage output = new BufferedImage(windowWidth, rowHeight, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g = output.createGraphics();

		configureRendered(g, true);

		Color stateColor = coffeeSaint.stateToColor(state, hard);
		Color gradientColor = config.getProblemRowGradient();
		if (gradientColor != null && stateColor != null) {
			configureRendered((Graphics2D)gTo, false);
			int scR = stateColor.getRed(), scG = stateColor.getGreen(), scB = stateColor.getBlue();
			int ftcR = gradientColor.getRed(), ftcG = gradientColor.getGreen(), ftcB = gradientColor.getBlue();
			double stepR = (double)(ftcR - scR) / (double)rowHeight;
			double stepG = (double)(ftcG - scG) / (double)rowHeight;
			double stepB = (double)(ftcB - scB) / (double)rowHeight;

			// double piStep = Math.PI / (double)rowHeight;
			for(int rowY = 0; rowY<rowHeight; rowY++)
			{
				//double pos = rowY * (1.0 - Math.sin(piStep * (double)rowY));
				double pos = rowY;
				int curR = Math.min(Math.max(0, scR + (int)((double)pos * stepR)), 255);
				int curG = Math.min(Math.max(0, scG + (int)((double)pos * stepG)), 255);
				int curB = Math.min(Math.max(0, scB + (int)((double)pos * stepB)), 255);

				g.setColor(new Color(curR, curG, curB));
				g.drawLine(0, rowY, windowWidth - 1, rowY);
			}
			configureRendered((Graphics2D)gTo, true);
		}
		else if (stateColor != null)
		{
			g.setColor(stateColor);
			g.fillRect(0, 0, windowWidth, rowHeight);
		}

		int flappingStartX = 0;
		if (isFlapping && config.getShowFlappingIcon()) {
			g.drawImage(flappingIcon, 0, 0, rowHeight, rowHeight, this);
			flappingStartX = rowHeight;
		}

		g.setColor(config.getTextColor());

		// draw text, with escapes
		g.setFont(rowParameters.getAdjustedFont());
		int newY = (int)rowParameters.getAsc();
		if (rowParameters.getShrunkMore() == true) {
			double heightDiff = rowParameters.getHeightDiff();
			newY = (int)(heightDiff / 2.0 + rowParameters.getAsc());
		}
		int textDrawX = flappingStartX + 1;
		Object [] parts = CoffeeSaint.splitStringWithFontstyleEscapes(msg);
		// dump(msg, parts);
		String [] strings = (String [])parts[0];
		Boolean [] bold = (Boolean [])parts[1];
		Boolean [] italic = (Boolean [])parts[2];
		Boolean [] underline = (Boolean [])parts[3];
		Boolean [] ss = (Boolean [])parts[4];
		Boolean [] useTabStop = (Boolean [])parts[5];
		int tabstop = 0, nTabStops = config.getLineScrollSplitter().size();
		for(int index=0; index<strings.length; index++) {
			Font cur = setFontStyle(rowParameters.getAdjustedFont(), bold[index], italic[index], ss[index], underline[index]);
			// find out where to start drawing
			if (useTabStop[index] != null && useTabStop[index] == true && tabstop < nTabStops)
				textDrawX = config.getLineScrollSplitter().get(tabstop++);
			// calculate available width
			int curSpace = windowWidth - textDrawX;
			if (tabstop < nTabStops && index < strings.length - 1 && useTabStop[index + 1] != null && useTabStop[index + 1] == true) {
				curSpace = config.getLineScrollSplitter().get(tabstop) - textDrawX;
				// System.out.println("curspace: " + curSpace);
			}
			// calculate needed width
			int curWidth = measureTextWidth(cur, strings[index], rowHeight);
			if (curSpace <= 0) {
				// don't do anything: there's no space
			}
			else
			{
				if (strings[index].length() > 0) {
					g.setClip(null);
					g.clip(new Rectangle(textDrawX, 0, curSpace, rowHeight));

					g.setFont(cur);
					g.drawString(strings[index], textDrawX, newY);
				}

				if (curWidth > curSpace)
					curWidth = curSpace;
			}
			textDrawX += curWidth;
		}
		// --- draw text ^

		if (sparkLine != null)
			g.drawImage(sparkLine, windowWidth - sparkLine.getWidth(), 0, null);

		Graphics2D gTo2D = (Graphics2D)gTo;

		int x = xStart;
		int y = 0;
		if (row > 0)
			y = rowHeight * row + (config.getShowHeader() ? config.getUpperRowBorderHeight() : 0);
		if (seeThrough != 1.0)
		{
			float [] scales  = { 1f, 1f, 1f, seeThrough };
			float [] offsets = { 0f, 0f, 0f, 0f   };
			gTo2D.drawImage(output, new RescaleOp(scales, offsets, null), x, y);
		}
		else
		{
			gTo2D.drawImage((Image)output, x, y, null);
		}
	}