From 4a3d23171fe4053fa484e106ae853192b1270059 Mon Sep 17 00:00:00 2001 From: Yana Stamcheva Date: Wed, 6 Jun 2007 16:55:43 +0000 Subject: [PATCH] advanced call history plugin --- build.xml | 18 +- lib/felix.client.run.properties | 9 +- lib/installer-exclude/jcalendar-1.3.2.jar | Bin 0 -> 126630 bytes .../AntialiasingManager.java | 33 ++ .../extendedcallhistorysearch/CallList.java | 85 ++++ .../CallListCellRenderer.java | 211 ++++++++ .../CallListModel.java | 156 ++++++ .../extendedcallhistorysearch/Constants.java | 137 +++++ .../ExtendedCallHistorySearchActivator.java | 61 +++ .../ExtendedCallHistorySearchDialog.java | 477 ++++++++++++++++++ .../ExtendedCallHistorySearchItem.java | 59 +++ .../GuiCallParticipantRecord.java | 119 +++++ .../extendedcallhistorysearch/GuiUtils.java | 205 ++++++++ .../extendedcallhistorysearch/Resources.java | 129 +++++ .../extendedcallhistorysearch.manifest.mf | 27 + .../resources.properties | 31 ++ .../resources/calendarIcon.png | Bin 0 -> 290 bytes .../resources/history16x16.png | Bin 0 -> 971 bytes .../resources/incomingCall.png | Bin 0 -> 1063 bytes .../resources/outgoingCall.png | Bin 0 -> 1360 bytes .../resources/searchIcon.png | Bin 0 -> 823 bytes 21 files changed, 1750 insertions(+), 7 deletions(-) create mode 100644 lib/installer-exclude/jcalendar-1.3.2.jar create mode 100644 src/net/java/sip/communicator/plugin/extendedcallhistorysearch/AntialiasingManager.java create mode 100644 src/net/java/sip/communicator/plugin/extendedcallhistorysearch/CallList.java create mode 100644 src/net/java/sip/communicator/plugin/extendedcallhistorysearch/CallListCellRenderer.java create mode 100644 src/net/java/sip/communicator/plugin/extendedcallhistorysearch/CallListModel.java create mode 100644 src/net/java/sip/communicator/plugin/extendedcallhistorysearch/Constants.java create mode 100644 src/net/java/sip/communicator/plugin/extendedcallhistorysearch/ExtendedCallHistorySearchActivator.java create mode 100644 src/net/java/sip/communicator/plugin/extendedcallhistorysearch/ExtendedCallHistorySearchDialog.java create mode 100644 src/net/java/sip/communicator/plugin/extendedcallhistorysearch/ExtendedCallHistorySearchItem.java create mode 100644 src/net/java/sip/communicator/plugin/extendedcallhistorysearch/GuiCallParticipantRecord.java create mode 100644 src/net/java/sip/communicator/plugin/extendedcallhistorysearch/GuiUtils.java create mode 100644 src/net/java/sip/communicator/plugin/extendedcallhistorysearch/Resources.java create mode 100644 src/net/java/sip/communicator/plugin/extendedcallhistorysearch/extendedcallhistorysearch.manifest.mf create mode 100644 src/net/java/sip/communicator/plugin/extendedcallhistorysearch/resources.properties create mode 100644 src/net/java/sip/communicator/plugin/extendedcallhistorysearch/resources/calendarIcon.png create mode 100644 src/net/java/sip/communicator/plugin/extendedcallhistorysearch/resources/history16x16.png create mode 100644 src/net/java/sip/communicator/plugin/extendedcallhistorysearch/resources/incomingCall.png create mode 100644 src/net/java/sip/communicator/plugin/extendedcallhistorysearch/resources/outgoingCall.png create mode 100644 src/net/java/sip/communicator/plugin/extendedcallhistorysearch/resources/searchIcon.png diff --git a/build.xml b/build.xml index 338b101c6..e2d1c150b 100644 --- a/build.xml +++ b/build.xml @@ -743,7 +743,7 @@ - + @@ -751,8 +751,8 @@ - + @@ -852,6 +852,7 @@ bundle-growlnotification,bundle-audionotifier,bundle-plugin-splashscreen, bundle-systray,bundle-browserlauncher,bundle-gibberish, bundle-gibberish-slick,bundle-plugin-gibberishaccregwizz, + bundle-plugin-extended-callhistory-search, bundle-rss,bundle-plugin-rssaccregwizz, bundle-pluginmanager"/> @@ -1545,6 +1546,17 @@ javax.swing.event, javax.swing.border"/> prefix="net/java/sip/communicator/plugin/pluginmanager"/> + + + + + + + + + diff --git a/lib/felix.client.run.properties b/lib/felix.client.run.properties index 0ef41b046..22493d539 100644 --- a/lib/felix.client.run.properties +++ b/lib/felix.client.run.properties @@ -71,8 +71,8 @@ felix.auto.start.50= \ reference:file:sc-bundles/protocol-yahoo.jar \ reference:file:sc-bundles/protocol-gibberish.jar \ reference:file:sc-bundles/netaddr.jar \ - reference:file:sc-bundles/meta-cl.jar \ - reference:file:sc-bundles/protocol-rss.jar + reference:file:sc-bundles/protocol-rss.jar \ + reference:file:sc-bundles/meta-cl.jar felix.auto.start.60= \ reference:file:sc-bundles/history.jar \ @@ -94,8 +94,9 @@ felix.auto.start.60= \ reference:file:sc-bundles/msnaccregwizz.jar \ reference:file:sc-bundles/yahooaccregwizz.jar \ reference:file:sc-bundles/gibberishaccregwizz.jar \ - reference:file:sc-bundles/shutdown.jar \ - reference:file:sc-bundles/rssaccregwizz.jar + reference:file:sc-bundles/extendedcallhistorysearch.jar \ + reference:file:sc-bundles/rssaccregwizz.jar \ + reference:file:sc-bundles/shutdown.jar # Uncomment the following lines if you want to run the architect viewer # bundle. diff --git a/lib/installer-exclude/jcalendar-1.3.2.jar b/lib/installer-exclude/jcalendar-1.3.2.jar new file mode 100644 index 0000000000000000000000000000000000000000..b5e5a2d824fee85ac095ea1833682d2ce6699186 GIT binary patch literal 126630 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
E{Eu6k>tt7+#WkuHK>!D6jMF*-Z6mrFBjhkI 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

~ z-4Ja!qh=od`;Xc8%lF{s$Qj$ocVIg`_Ck;JJr_;+(X;2iM3Tw%)RK<-=HiZL>O#Hd zGJ2wU-##rcQjs&pGV6mHV*!IWl8orI=&$|amIyrOW!bhKbG9M|a>g0$3k*3ml$1u> zGudB-$Whs-TynpAI{ zP~p{=h%xEaFc4Svue$b>l`(S0a#?e^_Qb1UBT_pa7>%=HjGU49y;o~byqo7@`LxZl zrqP&=$r*_^x?X$Y-8~b(_IUdSAs|f7NPOe3wI?3snRwvL6Dt;iy(VWQPAa6et%RO_w-D>Ptd&QD?yl?k$4o2;np_gy*v}2x$~irDJT+hy$qw+ zbpP5DkDZ)5yHGK><2jmL{g-(^joFl(aZ+swi%7N1LsinWOzpFx&FDo)J-P0N9E99f z6KQp6=o%imR98{!)lVc8MOj`z_>&+{&Nv8v)2|j$B>A0qv84 z)x@Ia!8PdAK>f3IKRk5{S`axS^$Hxj^H>8OsVkde<`S|T?^c?G!Y5~Jqup_6tya|C zn}KHHZ@jl;_FNDqXC!{2F2p^jTwk}O)}Z}uE?z$T1}U#ywZ3qVwP9ZrIyqwm^RaVP zW6_u+@SFp`V{^V)iZ(;eShGPmcvag7=(JLYY1vcFg>S!lWZMW73OOT__BFQpYD>NP z%00Af*@}BXo1BsU(usAW@40kNADnhz0ZN9Pk$yT3Gu|Nm+KQ}(eieSY#oZ-z#9lW&g0nsp%WIrV1fayR*lHzKcJU)E6m zW&mY(46nghR*e~`5`*VFn6X#gF?^O=hZw4_12cT8JAwsyb&SAs4$PRQ?g)n6af1kS zE5sTq!3?bEj-c)AI!53*2WA*HcLbAwI8b9P>vB+i6_|0<-1PrhNBULMPZDD@u$i0w zTL79cq~9&PHi1D^Wq*jW8OOy}eQ_-b*Sxefn>BYwaB)fX z5xBI3t6LYEEl9h`9$!`qvYrdWY-7kxRM=1pqBR$U*(#5lc)N|&6W3c?@tkP0#Tz%> z)|+ZY*I@g^a~x*V74A6pZ>dcjo~fG8_PVM5{9LtE^`}Lvk|jRgeCp3lw)>uH$!c7% zLSEf*s9`KLpFwg*BA5PwNUDw}loRtQ26rSK%j*_N^<$#>B!WAR6MrBM&-pMP5pc(` z=9L@f!!;DtP#b2gWp@Nw6?Kfja}Lb9kM0N-9H@N+)z^YqfzeIB@f&rcUp4(Sf0*?P m-Sj^;(dS#CuBE0<+i>_0Z^)b(Y=n$wzv0CG^5YOavHu5_d}!VP literal 0 HcmV?d00001 diff --git a/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/AntialiasingManager.java b/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/AntialiasingManager.java new file mode 100644 index 000000000..3763b9a2b --- /dev/null +++ b/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/AntialiasingManager.java @@ -0,0 +1,33 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ + +package net.java.sip.communicator.plugin.extendedcallhistorysearch; + +import java.awt.*; + +/** + * Through the AntialiasingManager the developer could activate the + * antialiasing mechanism when painting. The method that do the job is + * the activateAntialiasing method. It takes a Graphics + * object and activates the antialiasing for it. + * + * @author Yana Stamcheva + */ +public class AntialiasingManager { + + /** + * Activates the antialiasing mechanism for the given Graphics + * object. + * @param g The Graphics object. + */ + public static void activateAntialiasing(Graphics g) { + Graphics2D g2d = (Graphics2D) g; + + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + } +} diff --git a/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/CallList.java b/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/CallList.java new file mode 100644 index 000000000..4311ea8fd --- /dev/null +++ b/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/CallList.java @@ -0,0 +1,85 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.plugin.extendedcallhistorysearch; + +import java.awt.*; +import java.awt.event.*; + +import javax.swing.*; + +/** + * The CallList is the component that contains history call records. + * + * @author Yana Stamcheva + */ +public class CallList + extends JList + implements MouseListener +{ + public CallList() + { + this.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); + + this.getSelectionModel().setSelectionMode( + ListSelectionModel.SINGLE_SELECTION); + + this.setCellRenderer(new CallListCellRenderer()); + + this.setModel(new CallListModel()); + + this.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + + this.addMouseListener(this); + } + + public void addItem(Object item) + { + ((CallListModel)this.getModel()).addElement(item); + } + + public void addItem(Object item, int index) + { + ((CallListModel)this.getModel()).addElement(index, item); + } + + public void removeAll() + { + ((CallListModel)this.getModel()).removeAll(); + } + + /** + * Closes or opens a group of calls. + */ + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() > 1) { + + CallListModel listModel = (CallListModel) this.getModel(); + + Object element = listModel.getElementAt(this.getSelectedIndex()); + + if (element instanceof String) { + if (listModel.isDateClosed(element)) { + listModel.openDate(element); + } else { + listModel.closeDate(element); + } + } + } + } + + public void mouseEntered(MouseEvent e) + {} + + public void mouseExited(MouseEvent e) + {} + + public void mousePressed(MouseEvent e) + {} + + public void mouseReleased(MouseEvent e) + {} +} diff --git a/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/CallListCellRenderer.java b/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/CallListCellRenderer.java new file mode 100644 index 000000000..c77530fad --- /dev/null +++ b/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/CallListCellRenderer.java @@ -0,0 +1,211 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ + +package net.java.sip.communicator.plugin.extendedcallhistorysearch; + +import java.awt.*; + +import javax.swing.*; + +/** + * The ContactListCellRenderer is the custom cell renderer used in + * the SIP-Communicator's ContactList. It extends JPanel instead of + * JLabel, which allows adding different buttons and icons to the contact cell. + * The cell border and background are repainted. + * + * @author Yana Stamcheva + */ +public class CallListCellRenderer + extends JPanel + implements ListCellRenderer +{ + + private JPanel dataPanel = new JPanel(new BorderLayout()); + + private JLabel nameLabel = new JLabel(); + + private JPanel timePanel = new JPanel(new BorderLayout(8, 0)); + + private JLabel timeLabel = new JLabel(); + + private JLabel durationLabel = new JLabel(); + + private JLabel iconLabel = new JLabel(); + + private Icon incomingIcon = Resources.getImage("incomingCall"); + + private Icon outgoingIcon = Resources.getImage("outgoingCall"); + + private boolean isSelected = false; + + private boolean isLeaf = true; + + private String direction; + + /** + * Initialize the panel containing the node. + */ + public CallListCellRenderer() + { + + super(new BorderLayout(5, 5)); + + this.setBackground(Color.WHITE); + + this.setOpaque(true); + + this.dataPanel.setOpaque(false); + + this.timePanel.setOpaque(false); + + this.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); + + this.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + + this.nameLabel.setIconTextGap(2); + + this.nameLabel.setFont(this.getFont().deriveFont(Font.BOLD)); + + this.dataPanel.add(nameLabel, BorderLayout.WEST); + + this.add(dataPanel, BorderLayout.CENTER); + } + + /** + * Implements the ListCellRenderer method. + * + * Returns this panel that has been configured to display the meta contact + * and meta contact group cells. + */ + public Component getListCellRendererComponent(JList list, Object value, + int index, boolean isSelected, boolean cellHasFocus) + { + + this.dataPanel.remove(timePanel); + this.dataPanel.remove(timeLabel); + this.dataPanel.remove(durationLabel); + this.remove(iconLabel); + + if (value instanceof GuiCallParticipantRecord) + { + + GuiCallParticipantRecord participant = (GuiCallParticipantRecord) value; + + this.direction = participant.getDirection(); + + if (direction.equals(GuiCallParticipantRecord.INCOMING_CALL)) + iconLabel.setIcon(incomingIcon); + else + iconLabel.setIcon(outgoingIcon); + + this.nameLabel.setText(participant.getParticipantName()); + + this.timeLabel.setText(Resources.getString("at") + " " + + GuiUtils.formatTime(participant.getStartTime())); + + this.durationLabel.setText(Resources.getString("duration") + " " + + GuiUtils.formatTime(participant.getCallTime())); + + // this.nameLabel.setIcon(listModel + // .getMetaContactStatusIcon(contactItem)); + + this.timePanel.add(timeLabel, BorderLayout.WEST); + this.timePanel.add(durationLabel, BorderLayout.EAST); + this.dataPanel.add(timePanel, BorderLayout.EAST); + + this.add(iconLabel, BorderLayout.WEST); + + this.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1)); + + // We should set the bounds of the cell explicitely in order to + // make getComponentAt work properly. + this.setBounds(0, 0, list.getWidth() - 2, 25); + + this.isLeaf = true; + } + else if (value instanceof String) + { + + String dateString = (String) value; + + this.nameLabel.setText(dateString); + + this.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2)); + + // We should set the bounds of the cell explicitely in order to + // make getComponentAt work properly. + this.setBounds(0, 0, list.getWidth() - 2, 20); + + this.isLeaf = false; + } + + this.isSelected = isSelected; + + return this; + } + + /** + * Paint a background for all groups and a round blue border and background + * when a cell is selected. + */ + public void paintComponent(Graphics g) + { + super.paintComponent(g); + + Graphics2D g2 = (Graphics2D) g; + + AntialiasingManager.activateAntialiasing(g2); + + if (!this.isLeaf) + { + + GradientPaint p = new GradientPaint(0, 0, + Constants.BLUE_GRAY_BORDER_COLOR, this.getWidth(), this + .getHeight(), Constants.MOVER_END_COLOR); + + g2.setPaint(p); + g2.fillRoundRect(1, 1, this.getWidth(), this.getHeight() - 1, 7, 7); + } + else + { + if (direction.equals(GuiCallParticipantRecord.INCOMING_CALL)) + { + + GradientPaint p = new GradientPaint(0, 0, + Constants.HISTORY_IN_CALL_COLOR, this.getWidth(), this + .getHeight(), Constants.MOVER_END_COLOR); + + g2.setPaint(p); + g2.fillRoundRect(1, 1, this.getWidth(), this.getHeight() - 1, + 7, 7); + } + else if (direction.equals(GuiCallParticipantRecord.OUTGOING_CALL)) + { + + GradientPaint p = new GradientPaint(0, 0, + Constants.HISTORY_OUT_CALL_COLOR, this.getWidth(), this + .getHeight(), Constants.MOVER_END_COLOR); + + g2.setPaint(p); + g2.fillRoundRect(1, 1, this.getWidth(), this.getHeight() - 1, + 7, 7); + } + } + + if (this.isSelected) + { + + g2.setColor(Constants.SELECTED_END_COLOR); + g2.fillRoundRect(1, 0, this.getWidth(), this.getHeight(), 7, 7); + + g2.setColor(Constants.BLUE_GRAY_BORDER_DARKER_COLOR); + g2.setStroke(new BasicStroke(1.5f)); + g2.drawRoundRect(1, 0, this.getWidth() - 2, this.getHeight() - 1, + 7, 7); + } + } +} diff --git a/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/CallListModel.java b/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/CallListModel.java new file mode 100644 index 000000000..4a7d5d936 --- /dev/null +++ b/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/CallListModel.java @@ -0,0 +1,156 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.plugin.extendedcallhistorysearch; + +import java.util.*; + +import javax.swing.*; + +/** + * The data model of the Call list. + * + * @author Yana Stamcheva + */ +public class CallListModel extends AbstractListModel +{ + private LinkedList callList = new LinkedList(); + + private Hashtable closedDates = new Hashtable(); + + /** + * Closes the given date by hiding all containing calls. + * + * @param date The date to close. + */ + public void closeDate(Object date) + { + int startIndex = this.indexOf(date); + int endIndex = startIndex; + int currentSize = getSize(); + Collection c = new ArrayList(); + + for(int i = startIndex + 1; i < currentSize; i ++) { + Object o = this.getElementAt(i); + + if(o instanceof GuiCallParticipantRecord) { + this.closedDates.put(o, date); + c.add(o); + endIndex++; + } + else + break; + } + removeAll(c); + fireIntervalRemoved(this, startIndex, endIndex); + } + + /** + * Opens the given date by showing all containing calls. + * + * @param date The date to open. + */ + public void openDate(Object date) + { + int startIndex = this.indexOf(date); + int endIndex = startIndex; + Hashtable closedDatesCopy = (Hashtable)closedDates.clone(); + + if(closedDatesCopy.containsValue(date)) { + Iterator dates = closedDatesCopy.entrySet().iterator(); + + while(dates.hasNext()) { + Map.Entry entry = (Map.Entry)dates.next(); + Object callRecord = entry.getKey(); + Object callDate = entry.getValue(); + + if(callDate.equals(date)) { + endIndex++; + closedDates.remove(callRecord); + this.addElement(endIndex, callRecord); + } + } + } + fireIntervalAdded(this, startIndex, endIndex); + } + + /** + * Checks whether the given date is closed. + * + * @param date The date to check. + * @return True if the date is closed, false - otherwise. + */ + public boolean isDateClosed(Object date) { + if (this.closedDates.containsValue(date)) + return true; + else + return false; + } + + public int getSize() + { + return callList.size(); + } + + public Object getElementAt(int index) + { + if (index>=0) + return callList.get(index); + else + return null; + } + + public void addElement(Object item) + { + synchronized (callList) { + this.callList.add(item); + + int index = callList.indexOf(item); + fireIntervalAdded(this, index, index); + } + } + + public void addElement(int index, Object item) + { + synchronized (callList) { + this.callList.add(index, item); + fireIntervalAdded(this, index, index); + } + } + + public void removeElement(Object item) + { + synchronized (callList) { + this.callList.remove(item); + } + } + + public void removeAll(Collection c) + { + synchronized (callList) { + callList.removeAll(c); + } + } + + public void removeAll() + { + int currentSize = getSize(); + + while(callList.size() > 0) { + synchronized (callList) { + callList.removeLast(); + } + } + fireIntervalRemoved(this, 0, currentSize); + } + + public int indexOf(Object item) + { + synchronized (callList) { + return callList.indexOf(item); + } + } +} diff --git a/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/Constants.java b/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/Constants.java new file mode 100644 index 000000000..a3295ce58 --- /dev/null +++ b/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/Constants.java @@ -0,0 +1,137 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ + +package net.java.sip.communicator.plugin.extendedcallhistorysearch; + +import java.awt.*; + +import net.java.sip.communicator.util.*; + +/** + * All look and feel related constants are stored here. + * + * @author Yana Stamcheva + */ + +public class Constants { + + private static Logger logger = Logger.getLogger(Constants.class); + + /* + * =================================================================== + * ---------------------- CALLTYPE CONSTANTS ------------------------- + * =================================================================== + */ + + /** + * The incoming call flag. + */ + public static final int INCOMING_CALL = 1; + + /** + * The outgoing call flag. + */ + public static final int OUTGOING_CALL = 2; + + /** + * The Incoming & outcoming flag. + */ + public static final int INOUT_CALL = 3; + + /* + * ====================================================================== + * -------------------- FONTS AND COLOR CONSTANTS ------------------------ + * ====================================================================== + */ + + /** + * The color used to paint the background of an incoming call history + * record. + */ + public static final Color HISTORY_DATE_COLOR + = new Color(255, 201, 102); + + /** + * The color used to paint the background of an incoming call history + * record. + */ + public static final Color HISTORY_IN_CALL_COLOR + = new Color(249, 255, 197); + + /** + * The color used to paint the background of an outgoing call history + * record. + */ + public static final Color HISTORY_OUT_CALL_COLOR + = new Color(243, 244, 247); + + + /** + * The end color used to paint a gradient selected background of some + * components. + */ + public static final Color SELECTED_END_COLOR + = new Color(209, 212, 225); + + /** + * The start color used to paint a gradient mouse over background of some + * components. + */ + public static final Color MOVER_START_COLOR = new Color(230, + 230, 230); + + /** + * The end color used to paint a gradient mouse over background of some + * components. + */ + public static final Color MOVER_END_COLOR = new Color(255, + 255, 255); + + /** + * Gray color used to paint some borders, like the button border for + * example. + */ + public static final Color GRAY_COLOR = new Color(154, 154, + 154); + + /** + * A color between blue and gray used to paint some borders. + */ + public static final Color BLUE_GRAY_BORDER_COLOR = new Color(142, 160, 188); + + /** + * A color between blue and gray (darker than the other one), used to paint + * some borders. + */ + public static final Color BLUE_GRAY_BORDER_DARKER_COLOR = new Color(131, 149, + 178); + + + /* + * ====================================================================== + * --------------------------- FONT CONSTANTS --------------------------- + * ====================================================================== + */ + + /** + * The name of the font used in this ui implementation. + */ + public static final String FONT_NAME = "Verdana"; + + /** + * The size of the font used in this ui implementation. + */ + public static final String FONT_SIZE = "12"; + + /** + * The default Font object used through this ui implementation. + */ + public static final Font FONT = new Font(Constants.FONT_NAME, Font.PLAIN, + new Integer(Constants.FONT_SIZE).intValue()); + + +} diff --git a/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/ExtendedCallHistorySearchActivator.java b/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/ExtendedCallHistorySearchActivator.java new file mode 100644 index 000000000..80ccb9753 --- /dev/null +++ b/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/ExtendedCallHistorySearchActivator.java @@ -0,0 +1,61 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ + +package net.java.sip.communicator.plugin.extendedcallhistorysearch; + +import net.java.sip.communicator.service.callhistory.*; +import net.java.sip.communicator.service.gui.*; + +import org.osgi.framework.*; + +/** + * Call History Search PlugIn Activator + * + * @author Bourdon Maxime & Meyer Thomas + */ +public class ExtendedCallHistorySearchActivator + implements BundleActivator +{ + private static BundleContext context; + + public void start(BundleContext bc) throws Exception + { + context = bc; + ServiceReference uiServiceRef = bc.getServiceReference( + UIService.class.getName()); + + UIService uiService = (UIService) bc.getService(uiServiceRef); + + if (uiService.isContainerSupported(UIService.CONTAINER_TOOLS_MENU)) + { + ExtendedCallHistorySearchItem extendedSearch + = new ExtendedCallHistorySearchItem(); + + uiService.addComponent(UIService.CONTAINER_TOOLS_MENU, + extendedSearch); + } + } + + public void stop(BundleContext bc) throws Exception + { + } + + /** + * Returns an instance of the CallHistoryService. + */ + public static CallHistoryService getCallHistoryService() + { + ServiceReference callHistoryServiceRef = context + .getServiceReference(CallHistoryService.class.getName()); + + CallHistoryService callHistoryService = (CallHistoryService) context + .getService(callHistoryServiceRef); + + return callHistoryService; + } + +} diff --git a/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/ExtendedCallHistorySearchDialog.java b/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/ExtendedCallHistorySearchDialog.java new file mode 100644 index 000000000..f6ac0f640 --- /dev/null +++ b/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/ExtendedCallHistorySearchDialog.java @@ -0,0 +1,477 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ + +package net.java.sip.communicator.plugin.extendedcallhistorysearch; + +import java.awt.*; +import java.awt.event.*; +import java.util.*; + +import javax.swing.*; + +import net.java.sip.communicator.service.callhistory.*; + +import com.toedter.calendar.*; + +/** + * The ExtendedCallHistorySearchDialog allows to search in call + * history records, by specifying a period, or a call participant name, or type + * of the call (incoming or outgoing). + * + * @author Maxime Bourdon & Thomas Meyer + */ +public class ExtendedCallHistorySearchDialog + extends JDialog + implements ActionListener, + ItemListener +{ + /* PANEL */ + private JPanel mainSearchPanel = new JPanel(new BorderLayout()); + + private JPanel mainPanel = new JPanel(new BorderLayout(3, 1)); + + private JPanel searchPanel = new JPanel(new GridBagLayout()); + + private JPanel callTypePanel = new JPanel(new GridBagLayout()); + + private JPanel callListResultPanel = new JPanel(new BorderLayout()); + + /* BUTTON */ + private JButton searchButton = new JButton(Resources.getString("search"), + Resources.getImage("searchIcon")); + + /* TEXT FIELD */ + private JTextField contactNameField = new JTextField(); + + /* LABEL */ + private JLabel contactNameLabel = new JLabel(Resources + .getString("contactName") + ": "); + + private JLabel sinceDateLabel + = new JLabel(Resources.getString("since") + ": "); + + private JLabel untilDateLabel + = new JLabel(Resources.getString("until") + ": "); + + private JLabel callTypeLabel + = new JLabel(Resources.getString("callType") + ": "); + + /* CHECKBOX */ + private JCheckBox inCheckBox = new JCheckBox(Resources + .getString("incoming"), true); + + private JCheckBox outCheckBox = new JCheckBox(Resources + .getString("outgoing"), true); + + /* SCROLL PANE */ + private JScrollPane scrollPane = new JScrollPane(); + + /* contraint grid */ + private GridBagConstraints constraintsGRbag = new GridBagConstraints(); + + Collection participants = null; + + private CallList callList = new CallList(); + + /* Service */ + CallHistoryService callAccessService; + + private Date lastDateFromHistory = null; + + private Collection callListCollection; + + private JDateChooser untilDC + = new JDateChooser("dd/MM/yyyy", "##/##/####", ' '); + + private JDateChooser sinceDC + = new JDateChooser("dd/MM/yyyy", "##/##/####", ' '); + + private int direction; + + /** + * Creates a new instance of ExtendedCallHistorySearchDialog. + */ + public ExtendedCallHistorySearchDialog() + { + this.mainPanel.setPreferredSize(new Dimension(650, 550)); + + this.setTitle(Resources.getString("advancedCallHistorySearch")); + + this.initPanels(); + + this.initDateChooser(); + + this.searchPanel.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createTitledBorder("Search"), BorderFactory + .createEmptyBorder(5, 5, 5, 5))); + + this.callTypePanel.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createTitledBorder(""), BorderFactory + .createEmptyBorder(5, 5, 5, 5))); + + this.callListResultPanel.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createTitledBorder(""), BorderFactory + .createEmptyBorder(5, 5, 5, 5))); + + this.getContentPane().add(mainPanel); + this.pack(); + + /* action listener */ + searchButton.addActionListener(this); + inCheckBox.addItemListener(this); + outCheckBox.addItemListener(this); + + this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + } + + /** + * Loads all calls from history and shows them in this dialog. + */ + public void loadHistoryCalls() + { + /* get the call list collection */ + callAccessService = ExtendedCallHistorySearchActivator + .getCallHistoryService(); + callListCollection = callAccessService.findByEndDate(new Date()); + loadTableRecords( + callListCollection, Constants.INOUT_CALL, null, new Date()); + } + + /** + * Initialize the "until" date field to the current date. + */ + private void initDateChooser() + { + untilDC.getJCalendar().setWeekOfYearVisible(false); + untilDC.getJCalendar().setDate(new Date()); + sinceDC.getJCalendar().setWeekOfYearVisible(false); + } + + /** + * Init panels display. + */ + private void initPanels() + { + this.getRootPane().setDefaultButton(searchButton); + + this.mainSearchPanel.add(searchPanel, BorderLayout.NORTH); + this.mainSearchPanel.add(callTypePanel, BorderLayout.CENTER); + + this.mainPanel.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8)); + this.mainPanel.add(mainSearchPanel, BorderLayout.NORTH); + this.mainPanel.add(callListResultPanel, BorderLayout.CENTER); + + /* SEARCH PANEL */ + constraintsGRbag.anchor = GridBagConstraints.WEST; + constraintsGRbag.insets = new Insets(5, 5, 5, 5); + constraintsGRbag.gridwidth = 1; + constraintsGRbag.fill = GridBagConstraints.NONE; + this.searchPanel.add(contactNameLabel, constraintsGRbag); + + constraintsGRbag.gridwidth = GridBagConstraints.REMAINDER; + constraintsGRbag.fill = GridBagConstraints.HORIZONTAL; + constraintsGRbag.weightx = 1.0; + constraintsGRbag.gridx = GridBagConstraints.RELATIVE; + this.searchPanel.add(contactNameField, constraintsGRbag); + + /* DATE */ + constraintsGRbag.anchor = GridBagConstraints.WEST; + constraintsGRbag.gridwidth = 1; + constraintsGRbag.gridy = 2; + constraintsGRbag.fill = GridBagConstraints.NONE; + this.searchPanel.add(sinceDateLabel, constraintsGRbag); + + constraintsGRbag.gridwidth = GridBagConstraints.RELATIVE; + constraintsGRbag.fill = GridBagConstraints.HORIZONTAL; + constraintsGRbag.weightx = 1.0; + this.searchPanel.add(sinceDC, constraintsGRbag); + + constraintsGRbag.gridy = 3; + constraintsGRbag.gridwidth = 1; + constraintsGRbag.fill = GridBagConstraints.NONE; + this.searchPanel.add(untilDateLabel, constraintsGRbag); + + constraintsGRbag.fill = GridBagConstraints.HORIZONTAL; + this.searchPanel.add(untilDC, constraintsGRbag); + + /* BUTTON */ + constraintsGRbag.gridy = 4; + constraintsGRbag.gridx = 3; + constraintsGRbag.fill = GridBagConstraints.NONE; + constraintsGRbag.anchor = GridBagConstraints.EAST; + this.searchPanel.add(searchButton, constraintsGRbag); + + /* CALL TYPE */ + constraintsGRbag.anchor = GridBagConstraints.WEST; + constraintsGRbag.insets = new Insets(5, 5, 5, 5); + constraintsGRbag.gridwidth = 1; + constraintsGRbag.gridx = 1; + constraintsGRbag.gridy = 1; + this.callTypePanel.add(callTypeLabel, constraintsGRbag); + constraintsGRbag.gridx = 2; + this.callTypePanel.add(inCheckBox, constraintsGRbag); + constraintsGRbag.gridx = 3; + this.callTypePanel.add(outCheckBox, constraintsGRbag); + + /* CALL LIST PANEL */ + this.scrollPane.getViewport().add(callList); + this.callListResultPanel.add(scrollPane, BorderLayout.CENTER); + } + + /** + * Loads the appropriate history calls when user clicks on the search + * button. + */ + public void actionPerformed(ActionEvent e) + { + JButton sourceButton = (JButton) e.getSource(); + + if (sourceButton.equals(searchButton)) + { + /* update the callList */ + new Thread() + { + public void run() + { + callListCollection = callAccessService + .findByEndDate(new Date()); + + if (inCheckBox.isSelected() && outCheckBox.isSelected()) + { + direction = Constants.INOUT_CALL; + } + else + { + if (inCheckBox.isSelected()) + direction = Constants.INCOMING_CALL; + else + { + if (outCheckBox.isSelected()) + direction = Constants.OUTGOING_CALL; + else + direction = Constants.INOUT_CALL; + } + } + + SwingUtilities.invokeLater(new Runnable() + { + public void run() + { + loadTableRecords(callListCollection, direction, + sinceDC.getDate(), untilDC.getDate()); + } + }); + } + }.start(); + } + } + + /** + * Remove or add calls to the list of calls depending on the state of + * incoming and outgoing checkboxes. + */ + public void itemStateChanged(ItemEvent e) + { + JCheckBox sourceCheckBox = (JCheckBox) e.getSource(); + + /* Incoming checkbox */ + if (sourceCheckBox.equals(inCheckBox) + || sourceCheckBox.equals(outCheckBox)) + { + /* INCOMING box Checked */ + if (inCheckBox.isSelected()) + { + /* OUTCOMING box checked */ + if (outCheckBox.isSelected() == true) + { + loadTableRecords(callListCollection, + Constants.INOUT_CALL, + sinceDC.getDate(), untilDC.getDate()); + } + // only incoming is checked + else + { + loadTableRecords(callListCollection, + Constants.INCOMING_CALL, + sinceDC.getDate(), untilDC.getDate()); + } + } + /* check the OUTCOMING box */ + else + { + // checked + if (outCheckBox.isSelected() == true) + { + loadTableRecords(callListCollection, + Constants.OUTGOING_CALL, + sinceDC.getDate(), untilDC.getDate()); + } + /* both are unchecked */ + else + loadTableRecords(callListCollection, + Constants.INOUT_CALL, + sinceDC.getDate(), untilDC.getDate()); + } + } + } + + private String processDate(Date date) + { + String resultString; + long currentDate = System.currentTimeMillis(); + + if (GuiUtils.compareDates(date, new Date(currentDate)) == 0) + { + + resultString = Resources.getString("today"); + } + else + { + Calendar c = Calendar.getInstance(); + c.setTime(date); + + resultString = GuiUtils.formatDate(date); + } + + return resultString; + } + + /** + * Check if the callRecord direction equals the direction wanted by the user + * + * @param callType integer value (incoming = 1, outgoing = 2, in/out = 3) + * @param callrecord A Callrecord + * @return A string containing the callRecord direction if it equals the + * callType Call, null if not + */ + private String checkCallType(int callType, CallRecord callRecord) + { + String direction = null; + + // in + if (callRecord.getDirection().equals(CallRecord.IN) + && ((callType == Constants.INCOMING_CALL) + || callType == Constants.INOUT_CALL)) + direction = GuiCallParticipantRecord.INCOMING_CALL; + // out + else if (callRecord.getDirection().equals(CallRecord.OUT) + && (callType == Constants.OUTGOING_CALL + || callType == Constants.INOUT_CALL)) + direction = GuiCallParticipantRecord.OUTGOING_CALL; + + return direction; + } + + /** + * Check if sinceDate <= callStartDate <= beforeDate + * + * @param callStartDate + * @param sinceDate + * @param beforeDate + * @return true if sinceDate <= callStartDate <= beforeDate false if not + */ + private boolean checkDate(Date callStartDate, Date sinceDate, Date untilDate) + { + /* Test callStartDate >= sinceDate */ + if (sinceDate != null) + if (callStartDate.before(sinceDate)) + return false; + + /* Test callStartDate <= beforeDate */ + if (untilDate != null) + if (callStartDate.after(untilDate)) + return false; + + /* sinceDate <= callStartDate <= beforeDate */ + return true; + } + + /** + * Loads the collection of call records in the table. + * + * @param historyCalls the collection of call records + * @param calltype the type of the call - could be incoming or outgoing + * @param since the start date of the search + * @param before the end date of the search + */ + private void loadTableRecords(Collection historyCalls, int calltype, + Date since, Date before) + { + boolean addMe = true; + lastDateFromHistory = null; + callList.removeAll(); + // callList = new CallList(); + Iterator lastCalls = historyCalls.iterator(); + + while (lastCalls.hasNext()) + { + addMe = true; + + CallRecord callRecord = (CallRecord) lastCalls.next(); + + /* DATE Checking */ + Date callStartDate = callRecord.getStartTime(); + + if (checkDate(callStartDate, since, before)) + { + if (lastDateFromHistory == null) + { + callList.addItem(processDate(callStartDate)); + lastDateFromHistory = callStartDate; + } + else + { + int compareResult = GuiUtils.compareDates(callStartDate, + lastDateFromHistory); + + if (compareResult != 0) + { + callList.addItem(processDate(callStartDate)); + lastDateFromHistory = callStartDate; + } + } + } + else + addMe = false; + + /* PARTICIPANTS Checking */ + if (addMe) + { + Iterator participants = callRecord.getParticipantRecords() + .iterator(); + + while (participants.hasNext() && addMe) + { + CallParticipantRecord participantRecord + = (CallParticipantRecord) participants.next(); + + String participantName = participantRecord + .getParticipantAddress(); + + if (participantName.matches( + "(?i).*" + contactNameField.getText() + ".*")) + { + /* DIRECTION Checking */ + String direction; + direction = checkCallType(calltype, callRecord); + + if (direction != null) + callList.addItem(new GuiCallParticipantRecord( + participantRecord, direction)); + else + addMe = false; + } + else + addMe = false; // useless + } + } + } + + if (callList.getModel().getSize() > 0) + callList.addItem(Resources.getString("olderCalls") + "..."); + } +} diff --git a/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/ExtendedCallHistorySearchItem.java b/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/ExtendedCallHistorySearchItem.java new file mode 100644 index 000000000..af097ed7b --- /dev/null +++ b/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/ExtendedCallHistorySearchItem.java @@ -0,0 +1,59 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ + +package net.java.sip.communicator.plugin.extendedcallhistorysearch; + +import java.awt.*; +import java.awt.event.*; + +import javax.swing.*; + +/** + * The ExtendedCallHistorySearchButton is the button that will be + * added in the Call List panel and from which the user would be able to access + * the ExtendCallHistorySearchDialog. + * + * @author Bourdon Maxime & Meyer Thomas + */ +public class ExtendedCallHistorySearchItem + extends JMenuItem + implements + ActionListener +{ + private ExtendedCallHistorySearchDialog callHistorySearchDialog = null; + + /** + * Creates an instance of ExtendedCallHistoryButton. + */ + public ExtendedCallHistorySearchItem() + { + super(Resources.getString("advancedCallHistorySearch")); + + this.setMnemonic(Resources.getMnemonic("advancedCallHistorySearch")); + this.addActionListener(this); + } + + /** + * Launches the extended call history dialog when user clicks on this button. + */ + public void actionPerformed(ActionEvent e) + { + if (callHistorySearchDialog == null) + { + callHistorySearchDialog = new ExtendedCallHistorySearchDialog(); + callHistorySearchDialog.setLocation(Toolkit.getDefaultToolkit() + .getScreenSize().width + / 2 - callHistorySearchDialog.getWidth() / 2, Toolkit + .getDefaultToolkit().getScreenSize().height + / 2 - callHistorySearchDialog.getHeight() / 2); + } + + callHistorySearchDialog.loadHistoryCalls(); + + callHistorySearchDialog.setVisible(true); + } +} diff --git a/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/GuiCallParticipantRecord.java b/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/GuiCallParticipantRecord.java new file mode 100644 index 000000000..361ef8ced --- /dev/null +++ b/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/GuiCallParticipantRecord.java @@ -0,0 +1,119 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.plugin.extendedcallhistorysearch; + +import java.util.*; + +import net.java.sip.communicator.service.callhistory.*; + +/** + * The GuiCallParticipantRecord is meant to be used in the call history + * to represent a history call participant record. It wraps a + * CallParticipant or a CallParticipantRecord object. + * + * @author Yana Stamcheva + */ +public class GuiCallParticipantRecord +{ + public static final String INCOMING_CALL = "IncomingCall"; + + public static final String OUTGOING_CALL = "OutgoingCall"; + + private String direction; + + private String participantName; + + private Date startTime; + + private Date callTime; + + /** + * Creates an instance of GuiCallParticipantRecord by specifying + * the participant name, the call direction (incoming or outgoing), the + * time at which the call has started and the duration of the call. + * + * @param participantName the name of the call participant + * @param direction the direction of the call - INCOMING_CALL + * or OUTGOING_CALL + * @param startTime the time at which the call has started + * @param callTime the duration of the call + */ + public GuiCallParticipantRecord(String participantName, + String direction, + Date startTime, + Date callTime) + { + this.direction = direction; + + this.participantName = participantName; + + this.startTime = startTime; + + this.callTime = callTime; + } + + /** + * Creates an instance of GuiCallParticipantRecord by specifying + * the corresponding CallParticipantRecord, which gives all the + * information for the participant and the call duration. + * + * @param participantRecord the corresponding CallParticipantRecord + * @param direction the call direction - INCOMING_CALL or OUTGOING_CALL + */ + public GuiCallParticipantRecord(CallParticipantRecord participantRecord, + String direction) + { + this.direction = direction; + + this.participantName = participantRecord.getParticipantAddress(); + + this.startTime = participantRecord.getStartTime(); + + this.callTime = GuiUtils.substractDates( + participantRecord.getEndTime(), startTime); + } + + /** + * Returns the call direction - INCOMING_CALL or OUTGOING_CALL. + * + * @return the call direction - INCOMING_CALL or OUTGOING_CALL. + */ + public String getDirection() + { + return direction; + } + + /** + * Returns the duration of the call. + * + * @return the duration of the call + */ + public Date getCallTime() + { + return callTime; + } + + /** + * Returns the name of the participant. + * + * @return the name of the participant + */ + public String getParticipantName() + { + return participantName; + } + + /** + * Returns the time at which the call has started. + * + * @return the time at which the call has started + */ + public Date getStartTime() + { + return startTime; + } +} diff --git a/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/GuiUtils.java b/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/GuiUtils.java new file mode 100644 index 000000000..4b9b90e0f --- /dev/null +++ b/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/GuiUtils.java @@ -0,0 +1,205 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ + +package net.java.sip.communicator.plugin.extendedcallhistorysearch; + +import java.awt.*; +import java.util.*; + +import javax.swing.*; + + +/** + * The StringUtils class is used through this ui implementation for + * some special operations with strings. + * + * @author Yana Stamcheva + */ +public class GuiUtils { + + private static Calendar c1 = Calendar.getInstance(); + + private static Calendar c2 = Calendar.getInstance(); + + /** + * Replaces some chars that are special in a regular expression. + * @param text The initial text. + * @return the formatted text + */ + public static String replaceSpecialRegExpChars(String text) { + return text.replaceAll("([.()^&$*|])", "\\\\$1"); + } + + /** + * Returns the width in pixels of a text. + * @param c the component where the text is contained + * @param text the text to measure + * @return the width in pixels of a text. + */ + public static int getStringWidth(Component c, String text) { + return SwingUtilities.computeStringWidth(c + .getFontMetrics(Constants.FONT), text); + } + + /** + * Compares the two dates. The comparison is based only on the day, month + * and year values. Returns 0 if the two dates are equals, a value < 0 if + * the first date is before the second one and > 0 if the first date is after + * the second one. + * @param date1 the first date to compare + * @param date2 the second date to compare with + * @return Returns 0 if the two dates are equals, a value < 0 if + * the first date is before the second one and > 0 if the first date is after + * the second one + */ + public static int compareDates(Date date1, Date date2) + { + c1.setTime(date1); + c2.setTime(date2); + + int day1 = c1.get(Calendar.DAY_OF_MONTH); + int month1 = c1.get(Calendar.MONTH); + int year1 = c1.get(Calendar.YEAR); + + int day2 = c2.get(Calendar.DAY_OF_MONTH); + int month2 = c2.get(Calendar.MONTH); + int year2 = c2.get(Calendar.YEAR); + + if((day1 == day2) + && (month1 == month2) + && (year1 == year2)) { + + return 0; + } + else if((day1 < day2) + && (month1 <= month2) + && (year1 <= year2)) { + + return -1; + } + else { + return 1; + } + } + + /** + * Formats the given date. The result format is the following: + * [Month] [Day], [Year]. For example: Dec 24, 2000. + * @param date the date to format + * @return the formatted date string + */ + public static String formatDate(Date date) + { + c1.setTime(date); + + return GuiUtils.processMonth(c1.get(Calendar.MONTH) + 1) + " " + + GuiUtils.formatTime(c1.get(Calendar.DAY_OF_MONTH)) + ", " + + GuiUtils.formatTime(c1.get(Calendar.YEAR)); + } + + /** + * Formats the time for the given date. The result format is the following: + * [Hour]:[Minute]:[Second]. For example: 12:25:30. + * @param date the date to format + * @return the formatted hour string + */ + public static String formatTime(Date date) + { + c1.setTime(date); + + return GuiUtils.formatTime(c1.get(Calendar.HOUR_OF_DAY)) + ":" + + GuiUtils.formatTime(c1.get(Calendar.MINUTE)) + ":" + + GuiUtils.formatTime(c1.get(Calendar.SECOND)) ; + } + + /** + * Substracts the two dates. + * @param date1 the first date argument + * @param date2 the second date argument + * @return the date resulted from the substracting + */ + public static Date substractDates(Date date1, Date date2) + { + long d1 = date1.getTime(); + long d2 = date2.getTime(); + long difMil = d1-d2; + long milPerDay = 1000*60*60*24; + long milPerHour = 1000*60*60; + long milPerMin = 1000*60; + long milPerSec = 1000; + + long days = difMil / milPerDay; + int hour = (int)(( difMil - days*milPerDay ) / milPerHour); + int min + = (int)(( difMil - days*milPerDay - hour*milPerHour ) / milPerMin); + int sec + = (int)(( difMil - days*milPerDay - hour*milPerHour - min*milPerMin ) + / milPerSec); + + c1.clear(); + c1.set(Calendar.HOUR, hour); + c1.set(Calendar.MINUTE, min); + c1.set(Calendar.SECOND, sec); + + return c1.getTime(); + } + + /** + * Replaces the month with its abbreviation. + * @param month Value from 1 to 12, which indicates the month. + * @return the corresponding month abbreviation + */ + private static String processMonth(int month) + { + String monthString = ""; + if(month == 1) + monthString = Resources.getString("january"); + else if(month == 2) + monthString = Resources.getString("february"); + else if(month == 3) + monthString = Resources.getString("march"); + else if(month == 4) + monthString = Resources.getString("april"); + else if(month == 5) + monthString = Resources.getString("may"); + else if(month == 6) + monthString = Resources.getString("june"); + else if(month == 7) + monthString = Resources.getString("july"); + else if(month == 8) + monthString = Resources.getString("august"); + else if(month == 9) + monthString = Resources.getString("september"); + else if(month == 10) + monthString = Resources.getString("october"); + else if(month == 11) + monthString = Resources.getString("november"); + else if(month == 12) + monthString = Resources.getString("december"); + + return monthString; + } + + /** + * Adds a 0 in the beginning of one digit numbers. + * + * @param time The time parameter could be hours, minutes or seconds. + * @return The formatted minutes string. + */ + private static String formatTime(int time) + { + String timeString = new Integer(time).toString(); + + String resultString = ""; + if (timeString.length() < 2) + resultString = resultString.concat("0").concat(timeString); + else + resultString = timeString; + + return resultString; + } +} diff --git a/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/Resources.java b/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/Resources.java new file mode 100644 index 000000000..5e665afef --- /dev/null +++ b/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/Resources.java @@ -0,0 +1,129 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ + +package net.java.sip.communicator.plugin.extendedcallhistorysearch; + +import java.awt.image.*; +import java.io.*; +import java.util.*; + +import javax.imageio.*; +import javax.swing.*; + +import net.java.sip.communicator.util.*; +/** + * The Messages class manages the access to the internationalization + * properties files. + * @author Yana Stamcheva + */ +public class Resources { + + private static Logger log = Logger.getLogger(Resources.class); + + private static final String BUNDLE_NAME + = "net.java.sip.communicator.plugin.extendedcallhistorysearch.resources"; + + private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle + .getBundle(BUNDLE_NAME); + + /** + * Returns an internationalized string corresponding to the given key. + * @param key The key of the string. + * @return An internationalized string corresponding to the given key. + */ + public static String getString(String key) + { + String resourceString; + try + { + resourceString = RESOURCE_BUNDLE.getString(key); + + int mnemonicIndex = resourceString.indexOf('&'); + + if(mnemonicIndex > -1) + { + String firstPart = resourceString.substring(0, mnemonicIndex); + String secondPart = resourceString.substring(mnemonicIndex + 1); + + resourceString = firstPart.concat(secondPart); + } + } + catch (MissingResourceException e) + { + resourceString = '!' + key + '!'; + } + + return resourceString; + } + + /** + * Loads an image from a given image identifier. + * @param imageID The identifier of the image. + * @return The image for the given identifier. + */ + public static ImageIcon getImage(String imageID) { + BufferedImage image = null; + + String path = Resources.getString(imageID); + try { + image = ImageIO.read(Resources.class.getClassLoader() + .getResourceAsStream(path)); + + } catch (IOException e) { + log.error("Failed to load image:" + path, e); + } + + return new ImageIcon(image); + } + + /** + * Returns an internationalized string corresponding to the given key. + * @param key The key of the string. + * @return An internationalized string corresponding to the given key. + */ + public static char getMnemonic(String key) + { + String resourceString; + try { + resourceString = RESOURCE_BUNDLE.getString(key); + + int mnemonicIndex = resourceString.indexOf('&'); + + if(mnemonicIndex > -1) + { + return resourceString.charAt(mnemonicIndex + 1); + } + + } + catch (MissingResourceException e) + { + return '!'; + } + + return '!'; + } + + /** + * Loads an image from a given image identifier. + * @param imageID The identifier of the image. + * @return The image for the given identifier. + */ + public static byte[] getImageInBytes(String imageID) { + byte[] image = new byte[100000]; + + String path = Resources.getString(imageID); + try { + Resources.class.getClassLoader() + .getResourceAsStream(path).read(image); + + } catch (IOException e) { + log.error("Failed to load image:" + path, e); + } + + return image; + } +} diff --git a/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/extendedcallhistorysearch.manifest.mf b/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/extendedcallhistorysearch.manifest.mf new file mode 100644 index 000000000..b73a13ff4 --- /dev/null +++ b/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/extendedcallhistorysearch.manifest.mf @@ -0,0 +1,27 @@ +Bundle-Activator: net.java.sip.communicator.plugin.extendedcallhistorysearch.ExtendedCallHistorySearchActivator +Bundle-Name: Extended Call History Search +Bundle-Description: An Extended Call History Search +Bundle-Vendor: sip-communicator.org +Bundle-Version: 0.0.1 +Import-Package: org.osgi.framework, + net.java.sip.communicator.util, + net.java.sip.communicator.service.contactlist, + net.java.sip.communicator.service.contactlist.event, + net.java.sip.communicator.service.gui, + net.java.sip.communicator.service.gui.event, + net.java.sip.communicator.service.callhistory, + net.java.sip.communicator.service.configuration, + javax.swing, + javax.swing.event, + javax.swing.table, + javax.swing.text, + javax.accessibility, + javax.swing.plaf, + javax.swing.plaf.metal, + javax.swing.plaf.basic, + javax.imageio, + javax.swing.filechooser, + javax.swing.tree, + javax.swing.border + + diff --git a/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/resources.properties b/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/resources.properties new file mode 100644 index 000000000..017ff86f8 --- /dev/null +++ b/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/resources.properties @@ -0,0 +1,31 @@ +advancedCallHistorySearch=&Advanced call history search +search=Search +contactName=Contact Name +since=Since +until=Until +callType=Call Type +incoming=Incoming +outgoing=Outgoing +olderCalls=Older Calls +duration=Duration +at=at +all=all +today=Today +january=Jan +february=Feb +march=Mar +april=Apr +may=May +june=Jun +july=Jul +august=Aug +september=Sep +october=Oct +november=Nov +december=Dec + +searchIcon=net/java/sip/communicator/plugin/extendedcallhistorysearch/resources/searchIcon.png +historyMenuIcon=net/java/sip/communicator/plugin/extendedcallhistorysearch/resources/history16x16.png +incomingCall=net/java/sip/communicator/plugin/extendedcallhistorysearch/resources/incomingCall.png +outgoingCall=net/java/sip/communicator/plugin/extendedcallhistorysearch/resources/outgoingCall.png +calendarIcon=net/java/sip/communicator/plugin/extendedcallhistorysearch/resources/calendarIcon.png diff --git a/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/resources/calendarIcon.png b/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/resources/calendarIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..9cc1150933545c9b9492da3a2f7bfb727a08fab5 GIT binary patch literal 290 zcmeAS@N?(olHy`uVBq!ia0vp^0w6XEGms3L`dJ)E83g!*xGw*{!_(9ANlMC##KgC0 zX}=CfG&D5W+uQHH{r|v$1J92heSh-g)2B~gzI^%T&71%K|L@k`a|EcDu_VYZn8D%M zjWiG^$=lt9p@UV{1IS@7@$_|Nzs@SbuPnU&CHq#OP=lw7V@L%fV<|(|e;^kM*vr8H zs3SBqv!f$@0t5R@5SR{RoB@I3K*sqqe-~+ehcNUu!5E96j3rhO#-HJI9ORsQ6w#JU1_o1!gjW!*mo0DAPZVdyZ*J$Me+9u*S~4_w%uxC0$WU*4cSjiT z#9fFmv_^ELCk>G>)$?*vony(FM3!y`kz?TW&|8mss-_akD-XF(~25CTF7 zNQ5EUa}d`qodk6&P}LT^Jroay0xfSbN~C0WM;MBtVBZt3K+`n1t_#bu?g>oO1m_%2 z9(@m<=fN~hbajN`=kg)OC<)2b#mcz}sMLWLe;L8(0ob+;Aq1{3uH)U0zX7Eb1F_6AP?L>@IL&0KMccwZQG!fBAMvI{+=jgS%#{rP!t8KdIZ&K6)S}@8cwssgzIJJ z7fUfJs@PgM4SmmHbj1&Wa}EIDoP#k2&N*)W@e8awvv}~a*D!ypgoY!sq`XznWTtW@ zDz`w7K8-#5`tbY3Daf)6d;Jb7e_w&7X}Iy*Sv-<>3W3gNAW1%Cre;g4WivyHGvoQ< zhBf*1PdCiA&_lSMyMV^#5(puvUOfeA?hE++eo#@xitI~r tnS~@tg3}a*h9k18WixYo`gndj{|~Q#Z>rG9h#mj{002ovPDHLkV1gGb%3A;c literal 0 HcmV?d00001 diff --git a/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/resources/incomingCall.png b/src/net/java/sip/communicator/plugin/extendedcallhistorysearch/resources/incomingCall.png new file mode 100644 index 0000000000000000000000000000000000000000..f50b7058d9f112394b5858a85311693c7ba58a06 GIT binary patch literal 1063 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 BZvOxP literal 0 HcmV?d00001