package com.applang.berichtsheft.plugin; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Dimension; import java.awt.Font; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; import java.io.StringReader; import java.util.ArrayList; import java.util.NoSuchElementException; import java.util.Scanner; import java.util.regex.MatchResult; import java.util.regex.Pattern; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JSplitPane; import javax.swing.JTable; import org.gjt.sp.jedit.EBComponent; import org.gjt.sp.jedit.EBMessage; import org.gjt.sp.jedit.EditBus; import org.gjt.sp.jedit.View; import org.gjt.sp.jedit.gui.DefaultFocusComponent; import org.gjt.sp.jedit.gui.DockableWindowManager; import org.gjt.sp.jedit.msg.PropertiesChanged; import static com.applang.Util.*; import static com.applang.Util1.*; import static com.applang.SwingUtil.*; import static com.applang.PluginUtils.*; import android.app.AlertDialog; import android.content.ContentValues; import android.net.Uri; import android.util.Log; import com.applang.berichtsheft.BerichtsheftApp; import com.applang.components.DataView; import com.applang.components.DatePicker; import com.applang.components.ProfileManager; import com.applang.components.DataAdapter; import com.applang.components.ScriptManager; import com.applang.components.WeatherManager; import com.applang.provider.WeatherInfo; import com.applang.provider.WeatherInfo.Weathers; import console.Console; /** * * A dockable JPanel as a jEdit plugin. * */ public class DataDockable extends JPanel implements EBComponent, BerichtsheftActions, DefaultFocusComponent { private static final String TAG = DataDockable.class.getSimpleName(); private static final long serialVersionUID = 6415522692894321789L; private boolean floating; private View view; public class DataToolPanel extends JPanel { private JLabel label = new JLabel(); public DataToolPanel() { setLayout(new BoxLayout(this, BoxLayout.X_AXIS)); add(makeCustomButton("datadock.choose-uri", new ActionListener() { public void actionPerformed(ActionEvent evt) { chooseUri(); } }, false)); add(makeCustomButton("datadock.update-uri", new ActionListener() { public void actionPerformed(ActionEvent evt) { updateUri(); } }, false)); add(makeCustomButton("datadock.transport-to-buffer", new ActionListener() { public void actionPerformed(ActionEvent evt) { transportToBuffer(); } }, false)); add(makeCustomButton("datadock.transport-from-buffer", new ActionListener() { public void actionPerformed(ActionEvent evt) { final Console console = BerichtsheftShell.getConsole(true); if (console != null) { BerichtsheftShell.consoleWait(console, true); new AlertDialog(view, "Spinner test", "", "the spinner icon in the 'Berichtsheft' console window should be animated", JOptionPane.DEFAULT_OPTION, Behavior.NONE, null, new Job<Void>() { public void perform(Void t, Object[] parms) throws Exception { BerichtsheftShell.consoleWait(console, false); } }) .open(); } } }, true)); add(Box.createGlue()); Box labelBox = new Box(BoxLayout.Y_AXIS); labelBox.add(Box.createGlue()); propertiesChanged(); labelBox.add(label); labelBox.add(Box.createGlue()); add(labelBox); } void propertiesChanged() { String dbName = dataView.getUriString(); label.setText(dbName); boolean show = "true".equals(BerichtsheftPlugin.getOptionProperty("show-uri")); label.setVisible(show); } } private DataToolPanel toolPanel; /** * * @param view the current jedit window * @param position a variable passed in from the script in actions.xml, * which can be DockableWindowManager.FLOATING, TOP, BOTTOM, LEFT, RIGHT, etc. * see @ref DockableWindowManager for possible values. */ public DataDockable(View view, String position) { super(new BorderLayout()); this.view = view; this.floating = position.equals(DockableWindowManager.FLOATING); if (floating) this.setPreferredSize(new Dimension(500, 250)); add(BorderLayout.CENTER, dataView); this.toolPanel = new DataToolPanel(); add(BorderLayout.NORTH, this.toolPanel); propertiesChanged(); } public DataView dataView = BerichtsheftPlugin.getDataView(); public void focusOnDefaultComponent() { dataView.requestFocus(); } // EBComponent implementation public void handleMessage(EBMessage message) { if (message instanceof PropertiesChanged) { propertiesChanged(); } } void propertiesChanged() { Font newFont = BerichtsheftOptionPane.makeFont(); JTable table = dataView.getTable(); Font oldFont = table.getFont(); if (!newFont.equals(oldFont)) { table.setFont(newFont); } if (dataView.getUri() == null) dataView.load(); else dataView.reload(); toolPanel.propertiesChanged(); } // These JComponent methods provide the appropriate points // to subscribe and unsubscribe this object to the EditBus. @Override public void addNotify() { super.addNotify(); EditBus.addToBus(this); } @Override public void removeNotify() { super.removeNotify(); EditBus.removeFromBus(this); } // Actions implementation public void chooseUri() { if (dataView.configureData(view, true)) { setProperty("TRANSPORT_URI", dataView.getUriString()); updateUri(); } }; public void updateUri() { dataView.nosync(); NoteDockable dockable = (NoteDockable) BerichtsheftPlugin.getDockable(view, "notedock", false); if (dockable != null) dockable.propertiesChanged(); toolPanel.propertiesChanged(); } public void spellCheckSelection() { doTransport(view, "spellcheck"); } public void transportFromBuffer() { setProperty("TRANSPORT_OPER", "pull"); invokeAction(view, "commando.Transport"); } public void transportToBuffer() { JTable table = dataView.getTable(); if (hasNoSelection(table)) { BerichtsheftPlugin.consoleMessage("datadock.transport-to-buffer.message"); return; } ValList columns = DataView.getSelectedColumnNames(table); TransportBuilder builder = new TransportBuilder(); String template = builder.makeTemplate(columns.toArray()); columns = builder.evaluateTemplate(template, null); if (notAvailable(0, columns)) return; final String text = builder.wrapRecords(table); showItems(view, "datadock.transport-to-buffer.label", getProperty("datadock.transport-to-buffer.message.1"), text, JOptionPane.OK_CANCEL_OPTION, Behavior.NONE, new Job<Void>() { public void perform(Void t, Object[] params) throws Exception { BerichtsheftPlugin.getJEditor().setSelectedText(text); } }); } public static class TransportBuilder { static String CLIPPER = "`"; public static Pattern TEMPLATE_PATTERN = clippingPattern(CLIPPER, CLIPPER); public ValList evaluateTemplate(String template, ValMap profile) { MatchResult[] mr = notNullOrEmpty(template) ? findAllIn(template, TEMPLATE_PATTERN) : null; if (nullOrEmpty(mr)) { BerichtsheftPlugin.consoleMessage("datadock.template-evaluation.message.1"); return null; } getTemplateOptions(profile, 0); ValList projection = vlist(); ArrayList<String> list = alist(); int pos = 0; for (int i = 0; i < mr.length; i++) { MatchResult m = mr[i]; if (pos != m.start()) { BerichtsheftPlugin.consoleMessage("datadock.template-evaluation.message.2"); return null; } String s = m.group(1); if (i == 0) list.add(s); s = m.group(3); if (i < mr.length - 1) s += mr[i+1].group(1); list.add(s); projection.add(m.group(2)); pos = m.end(); } if (pos != template.length()) { BerichtsheftPlugin.consoleMessage("datadock.template-evaluation.message.2"); return null; } fieldSeparators = list.toArray(strings()); fieldSeparatorPatterns = new Pattern[fieldSeparators.length]; for (int i = 0; i < fieldSeparators.length; i++) { String pat = fieldSeparators[i]; if (pat.contains(NEWLINE)) { String[] parts = pat.split(NEWLINE_REGEX, -2); pat = ""; for (int j = 0; j < parts.length; j++) { if (j > 0) pat += WHITESPACE_OR_NOTHING_REGEX + "?" + NEWLINE_REGEX; String p = parts[j]; if (p.length() > 0) pat += Pattern.quote(p); } } else pat = Pattern.quote(pat); if (i == 0) { if (isWhiteSpace(fieldSeparators[i])) pat = WHITESPACE_OR_NOTHING_REGEX; pat = "^" + pat; } else if (i == fieldSeparators.length - 1) pat += "$"; fieldSeparatorPatterns[i] = Pattern.compile(pat); } return projection; } public BidiMultiMap elaborateProjection(Object[] fields, ValList names, String tableName) { BidiMultiMap projection = new BidiMultiMap(); for (int i = 0; i < fields.length; i++) { String[] parts = stringValueOf(fields[i]).split("\\|", 2); String field, func = null; switch (parts.length) { case 2: func = parts[1]; case 1: field = parts[0]; break; default: continue; } if (names != null && !names.contains(field)) { BerichtsheftPlugin.consoleMessage("datadock.transport-check.message", field, tableName); return null; } projection.add(field, func); } return projection; } public String makeTemplate(Object[] projection) { int length = projection.length; if (length < 1) return null; getTemplateOptions(null, length); String template = fieldSeparators[0]; for (int i = 0; i < length; i++) { template += enclose(CLIPPER, projection[i].toString()); template += fieldSeparators[i + 1]; } return template; } public String wrapRecords(JTable table) { boolean isCellSelection = table.getCellSelectionEnabled(); int[] rows = table.getSelectedRows(); int[] cols = table.getSelectedColumns(); int length = isCellSelection ? cols.length : table.getColumnCount(); DataView.DataModel model = (DataView.DataModel) table.getModel(); ValList records = vlist(); for (int i = 0; i < rows.length; i++) { String rec = recordDecoration[0] + fieldSeparators[0]; int row = rows[i]; row = table.convertRowIndexToModel(row); Object[] values = model.getValues(true, row); for (int j = 0; j < length; j++) { int col = isCellSelection ? cols[j] : j; rec += stringValueOf(values[col]); rec += fieldSeparators[j + 1]; } rec += recordDecoration[1]; records.add(rec); } return join(recordSeparator[0], records.toArray()); } public String[] fieldSeparators, recordDecoration; public Pattern[] fieldSeparatorPatterns; public String[] fieldSeparator, recordSeparator; private void getTemplateOptions(ValMap profile, int length) { Object key = BerichtsheftPlugin.getOptionProperty("field-separator"); fieldSeparator = (String[]) BerichtsheftOptionPane.separators.get(key); ArrayList<String> list = alist(); list.add(""); for (int i = 0; i < length - 1; i++) { list.add(fieldSeparator[0]); } list.add(""); fieldSeparators = list.toArray(strings()); if (profile != null && profile.containsKey("recordSeparator")) key = profile.get("recordSeparator"); else key = BerichtsheftPlugin.getOptionProperty("record-separator"); recordSeparator = (String[]) BerichtsheftOptionPane.separators.get(key); if (profile != null && profile.containsKey("recordDecoration")) key = profile.get("recordDecoration"); else key = BerichtsheftPlugin.getOptionProperty("record-decoration"); recordDecoration = (String[]) BerichtsheftOptionPane.decorations.get(key); } public DataView.DataModel scan(Readable input, BidiMultiMap projection) { DataView.DataModel model = new DataView.DataModel().setProjection(projection); Scanner scanner = new Scanner(input); String delimiter = null; try { scanner.skip(Pattern.quote(recordDecoration[0])); delimiter = Pattern.quote(recordDecoration[1]) + recordSeparator[1] + Pattern.quote(recordDecoration[0]); scanner.useDelimiter(delimiter); int no = 0; do { String record = scanner.next(); if (!scanner.hasNext()) { MatchResult mr = findFirstIn(record, Pattern.compile(Pattern.quote(recordDecoration[1]))); if (mr == null) { BerichtsheftPlugin.consoleMessage("datadock.scan.message.1"); return null; } record = record.substring(0, mr.start()); } ValList values = splitRecord(record, projection.getKeys().toArray(), ++no); if (values == null) return null; model.addValues(true, values.toArray()); } while (scanner.hasNext()); } catch (NoSuchElementException e) { String msg = delimiter == null ? "datadock.scan.message.2" : "datadock.scan.message.3"; BerichtsheftPlugin.consoleMessage(msg); return null; } finally { scanner.close(); } return model; } private ValList splitRecord(String record, Object[] fieldNames, int no) { MatchResult mr = findFirstIn(record, fieldSeparatorPatterns[0]); if (mr == null) { BerichtsheftPlugin.consoleMessage("datadock.split.message.1", no); return null; } ValList values = vlist(); for (int i = 0; i < fieldNames.length; i++) { record = record.substring(mr.end()); Pattern pattern = fieldSeparatorPatterns[i + 1]; mr = findFirstIn(record, pattern); if (mr == null) { BerichtsheftPlugin.consoleMessage("datadock.split.message.2", no, fieldNames[i]); return null; } String field = record.substring(0, mr.start()); values.add(field); } return values; } } public static int showItems(View view, String titleProperty, String caption, Object message, int optionType, int behavior, Job<Void> followUp, Object...params) { if (message instanceof JTable) { JTable table = (JTable) message; int sel; switch (sel = param_Integer(-3, 0, params)) { case -3: break; case -2: table.setEnabled(false); break; case -1: table.selectAll(); break; default: table.getSelectionModel().setSelectionInterval(sel, sel); break; } message = scrollableViewport(table, new Dimension(800,200)); } return new AlertDialog(view, getProperty(titleProperty), caption, message, optionType, behavior, null, followUp).open().getResult(); } // NOTE used in scripts public static boolean doTransport(final View view, String oper, Object...params) { boolean showData = param_Boolean(true, 0, params); DataDockable dockable = (DataDockable) BerichtsheftPlugin.getDockable(view, "datadock", false); if (dockable == null) { BerichtsheftPlugin.consoleMessage("datadock.dockable-required.message"); return false; } final String uriString = getProperty("TRANSPORT_URI"); if (nullOrEmpty(uriString)) { BerichtsheftPlugin.consoleMessage("datadock.transport-uri.message"); return false; } DataAdapter dataAdapter = new DataAdapter(uriString); boolean retval = true; try { if (dockable != null) dockable.dataView.wireObserver(dockable.dataView.getContext().getContentResolver(), true); final TransportBuilder builder = new TransportBuilder(); if ("push".equals(oper)) { ValMap profile = ProfileManager.getProfileAsMap(); String template = stringValueOf(profile.get("template")); ValList list = builder.evaluateTemplate(template, profile); retval = isAvailable(0, list); if (retval) { BidiMultiMap projection = builder.elaborateProjection(list.toArray(), dataAdapter.info.getList("name"), dataAdapter.getTableName()); if (projection == null) return false; DataView.DataModel model = dataAdapter.query(uriString, projection, profile.get("filter")); if (model == null) return false; final JTable table = model.makeTable(); Job<Void> pushThis = new Job<Void>() { public void perform(Void t, Object[] params) throws Exception { String text = builder.wrapRecords(table); BerichtsheftPlugin.getJEditor().setSelectedText(text); } }; if (showData) { showItems(view, "datadock.transport-to-buffer.label", String.format("%d record(s)", model.getRowCount()), table, JOptionPane.OK_CANCEL_OPTION, Behavior.NONE, pushThis, -1); } else pushThis.perform(null, null); } dockable = null; } else if ("pull".equals(oper)) { String text = BerichtsheftPlugin.getJEditor().getSelectedText(); if (nullOrEmpty(text)) { BerichtsheftPlugin.consoleMessage("berichtsheft.no-text-selection.message"); return false; } ValMap profile = ProfileManager.getProfileAsMap(); String template = stringValueOf(profile.get("template")); ValList list = builder.evaluateTemplate(template, profile); retval = isAvailable(0, list); if (retval) { BidiMultiMap projection = builder.elaborateProjection(list.toArray(), dataAdapter.info.getList("name"), dataAdapter.getTableName()); if (projection == null) return false; DataView.DataModel model = builder.scan(new StringReader(text), projection); if (null == model) { BerichtsheftPlugin.consoleMessage("datadock.transport-fail.message", oper, "no data"); return false; } JTable table = model.makeTable(); if (showData) { retval = JOptionPane.OK_OPTION == showItems(view, "datadock.transport-from-buffer.label", String.format("%d record(s)", model.getRowCount()), table, JOptionPane.OK_CANCEL_OPTION, Behavior.MODAL, null, -1); } if (retval) { int[] results = dataAdapter.pickRecords(view, table, uriString, profile); retval = results != null; if (retval) BerichtsheftPlugin.consoleMessage("dataview.updateOrInsert.message", results[0], results[1]); } } } else if ("download".equals(oper)) { ValMap profile = ProfileManager.getProfileAsMap(); String url = stringValueOf(profile.get("url")); final String text = readFromUrl(url, "UTF-8"); Job<Void> pushThis = new Job<Void>() { public void perform(Void t, Object[] params) throws Exception { BerichtsheftPlugin.getJEditor().setSelectedText(text); } }; if (showData) { showItems(view, "datadock.download-to-buffer.label", getProperty("datadock.transport-to-buffer.message.1"), text, JOptionPane.OK_CANCEL_OPTION, Behavior.NONE, pushThis); } else pushThis.perform(null, null); } } catch (Exception e) { Log.e(TAG, "doTransport", e); } finally { if (dockable != null) dockable.dataView.reload(); } return retval; } // NOTE used in scripts public static boolean makeWetter(final View view, String oper, Object...params) { boolean showData = param_Boolean(true, 0, params); String dbPath = dbPath(param_String("", 1, params)); Boolean async = param_Boolean(true, 2, params); boolean retval = true; if ("period".equals(oper)) { final String uriString = Weathers.CONTENT_URI.toString(); final DataAdapter dataAdapter = new DataAdapter(WeatherInfo.AUTHORITY, new File(dbPath), uriString); final int[] results = ints(0,0,0); final ValMap profile = ProfileManager.getProfileAsMap("_weather", "download"); ValMap map = ScriptManager.getProjectionDefault(profile.get("flavor"), dataAdapter.getTableName()); final BidiMultiMap projection = (BidiMultiMap) map.get("projection"); final Object pk = dataAdapter.info.get("PRIMARY_KEY"); projection.removeKey(pk); WeatherManager wm = new WeatherManager() { @Override public Object updateOrInsert(String location, long time, ValMap values) { ValList list = vlist(); for (Object key : projection.getKeys()) { if (Weathers.LOCATION.equals(key)) list.add(location); else if (Weathers.CREATED_DATE.equals(key)) list.add(time); else if (Weathers.MODIFIED_DATE.equals(key)) list.add(now()); else if (values.containsKey(key)) list.add(values.get(key)); } Object[] items = list.toArray(); ContentValues contentValues = contentValues(dataAdapter.info, projection.getKeys(), items); Object result = dataAdapter.updateOrInsert(uriString, profile, projection, pk, contentValues, dataAdapter.skipThis(view, items)); if (!dataAdapter.checkResult(result, ++results[2])) result = null; else if (result instanceof Uri) results[0]++; else if (result != null) results[1] += (Integer) result; return result; } }; wm.parseAndEvaluate(wm.location, DatePicker.Period.loadParts(0), showData, new Function<Void>() { public Void apply(Object... params) { int[] results = param(null, 0, params); if (results != null) { if (results[0] > 0 || results[1] > 0) BerichtsheftPlugin.consoleMessage("dataview.updateOrInsert.message", results[0], results[1]); } return null; } }, results, async); } return retval; } // NOTE used in scripts public static boolean makeDokument(final View view, String oper, Object...params) { boolean keep = param_Boolean(false, 0, params); boolean retval = false; if ("odt".equals(oper)) { String dbPath = param_String(null, 1, params); String dbPath2 = param_String(null, 2, params); DatePicker.Period.load(1); String dateString = DatePicker.Period.weekDate(); int[] weekDate = DatePicker.parseWeekDate(dateString); String docPath = BerichtsheftApp.odtDokumentPath("Tagesberichte", weekDate); if (BerichtsheftApp.export( BerichtsheftApp.odtVorlagePath("Tagesberichte"), docPath, strings(dbPath,dbPath2), weekDate[1], weekDate[0], "\\d", keep)) { retval = true; } else { BerichtsheftPlugin.consoleMessage("berichtsheft.export-document.message.2"); } BerichtsheftPlugin.consoleMessage("berichtsheft.export-document.message.1", docPath); } return retval; } public static void main(String...args) { BerichtsheftApp.loadSettings(); final DataDockable dd = new DataDockable(null, DockableWindowManager.FLOATING); showFrame(null, "Data", new UIFunction() { public Component[] apply(final Component comp, Object[] parms) { JSplitPane sp = splitPane(JSplitPane.VERTICAL_SPLIT); sp.setTopComponent(dd); sp.setBottomComponent(new JPanel()); return components(sp); } }, null, null, Behavior.EXIT_ON_CLOSE); } }